solargraph
Advanced tools
| --- | ||
| # To debug locally: | ||
| # npm install -g act | ||
| # act pull_release -j overcommit | ||
| name: Linting | ||
| on: | ||
| workflow_dispatch: {} | ||
| pull_request: | ||
| branches: [ master ] | ||
| push: | ||
| branches: | ||
| - 'main' | ||
| tags: | ||
| - 'v*' | ||
| permissions: | ||
| pull-requests: write | ||
| contents: read | ||
| jobs: | ||
| overcommit: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v2 | ||
| # Number of commits to fetch. 0 indicates all history for all branches and tags. | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: ruby/setup-ruby@v1 | ||
| with: | ||
| ruby-version: 3.4 | ||
| bundler: latest | ||
| bundler-cache: true | ||
| cache-version: 2025-06-06 | ||
| - name: Update to best available RBS | ||
| run: | | ||
| bundle update rbs # use latest available for this Ruby version | ||
| - name: Restore cache of gem annotations | ||
| id: dot-cache-restore | ||
| uses: actions/cache/restore@v4 | ||
| with: | ||
| key: | | ||
| 2025-06-26-09-${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} | ||
| restore-keys: | | ||
| 2025-06-26-09-${{ runner.os }}-dot-cache | ||
| 2025-06-26-09-${{ runner.os }}-dot-cache- | ||
| path: | | ||
| /home/runner/.cache/solargraph | ||
| - name: Install gem types | ||
| run: bundle exec rbs collection install | ||
| - name: Overcommit | ||
| run: | | ||
| bundle exec overcommit --sign | ||
| SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master | ||
| rubocop: | ||
| name: rubocop | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - uses: ruby/setup-ruby@1a615958ad9d422dd932dc1d5823942ee002799f # v1.227.0 | ||
| with: | ||
| ruby-version: '3.3' | ||
| bundler-cache: true | ||
| - uses: reviewdog/action-rubocop@fcb74ba274da10b18d038d0bcddaae3518739634 # v2.21.2 | ||
| with: | ||
| reporter: github-pr-check | ||
| skip_install: true | ||
| use_bundler: true | ||
| rubocop_extensions: 'rubocop-rspec:gemfile rubocop-rake:gemfile rubocop-yard:gemfile' | ||
| rubocop_flags: '-c .rubocop.yml' | ||
| fail_level: info | ||
| rubocop_version: Gemfile | ||
| level: info | ||
| rbs_validate: | ||
| name: rbs validate | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| - name: Set up Ruby | ||
| uses: ruby/setup-ruby@v1 | ||
| with: | ||
| ruby-version: 3.4 | ||
| bundler-cache: false | ||
| - name: Install gems | ||
| run: bundle install | ||
| - name: Run rbs validate | ||
| run: bundle exec rbs validate | ||
| rubocop_todo: | ||
| name: .rubocop_todo.yml | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| - name: Set up Ruby | ||
| uses: ruby/setup-ruby@v1 | ||
| with: | ||
| ruby-version: 3.4 | ||
| bundler-cache: false | ||
| - name: Install gems | ||
| run: bundle install | ||
| - name: Run RuboCop | ||
| run: bundle exec rubocop -c .rubocop.yml | ||
| - name: Run RuboCop against todo file | ||
| continue-on-error: true | ||
| run: | | ||
| bundle exec rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp | ||
| if [ -n "$(git status --porcelain)" ] | ||
| then | ||
| git status --porcelain | ||
| git diff -u . | ||
| >&2 echo "Please fix deltas if bad or run 'bundle exec rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp' and push up changes if good" | ||
| exit 1 | ||
| fi |
| --- | ||
| # Use this file to configure the Overcommit hooks you wish to use. This will | ||
| # extend the default configuration defined in: | ||
| # https://github.com/sds/overcommit/blob/master/config/default.yml | ||
| # | ||
| # At the topmost level of this YAML file is a key representing type of hook | ||
| # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can | ||
| # customize each hook, such as whether to only run it on certain files (via | ||
| # `include`), whether to only display output if it fails (via `quiet`), etc. | ||
| # | ||
| # For a complete list of hooks, see: | ||
| # https://github.com/sds/overcommit/tree/master/lib/overcommit/hook | ||
| # | ||
| # For a complete list of options that you can use to customize hooks, see: | ||
| # https://github.com/sds/overcommit#configuration | ||
| # | ||
| # Uncomment the following lines to make the configuration take effect. | ||
| PreCommit: | ||
| RuboCop: | ||
| enabled: true | ||
| Solargraph: | ||
| enabled: true | ||
| exclude: | ||
| - 'spec/**/*' | ||
| FixMe: | ||
| enabled: true | ||
| exclude: | ||
| # don't freak out over line below | ||
| - '.overcommit.yml' | ||
| # from upstream | ||
| - 'rbs/fills/rubygems/0/basic_specification.rbs' | ||
| keywords: ['FIXME', 'TODO', 'XXX'] | ||
| # creates false positives in CI | ||
| AuthorName: | ||
| enabled: false | ||
| # creates false positives in CI | ||
| AuthorEmail: | ||
| enabled: false | ||
| ALL: | ||
| # if you change the next line to 'report', and you make changes to | ||
| # a file, overcommit will have you make the entire file pass | ||
| # | ||
| problem_on_unmodified_line: warn | ||
| # on_warn: fail | ||
| exclude: | ||
| - lib/solargraph/rails/annotations/**/* | ||
| - vendor/**/* | ||
| - ".bundle/**/*" | ||
| - 'core.*' | ||
| verify_signatures: false | ||
| # | ||
| # TrailingWhitespace: | ||
| # enabled: true | ||
| # exclude: | ||
| # - '**/db/structure.sql' # Ignore trailing whitespace in generated files | ||
| # | ||
| #PostCheckout: | ||
| # ALL: # Special hook name that customizes all hooks of this type | ||
| # quiet: true # Change all post-checkout hooks to only display output on failure | ||
| # | ||
| # IndexTags: | ||
| # enabled: true # Generate a tags file with `ctags` each time HEAD changes | ||
| gemfile: Gemfile |
Sorry, the diff of this file is too big to display
+66
| --- | ||
| inherit_from: .rubocop_todo.yml | ||
| # The behavior of RuboCop can be controlled via the .rubocop.yml | ||
| # configuration file. It makes it possible to enable/disable | ||
| # certain cops (checks) and to alter their behavior if they accept | ||
| # any parameters. The file can be placed either in your home | ||
| # directory or in some project directory. | ||
| # | ||
| # RuboCop will start looking for the configuration file in the directory | ||
| # where the inspected file is and continue its way up to the root directory. | ||
| # | ||
| # See https://docs.rubocop.org/rubocop/configuration | ||
| AllCops: | ||
| NewCops: enable | ||
| Exclude: | ||
| - "spec/fixtures/invalid_byte.rb" | ||
| - "spec/fixtures/invalid_node_comment.rb" | ||
| - "spec/fixtures/invalid_utf8.rb" | ||
| - "vendor/**/*" | ||
| - "vendor/**/.*" | ||
| TargetRubyVersion: 3.0 | ||
| RSpec/SpecFilePathFormat: | ||
| Enabled: false | ||
| Style/MethodDefParentheses: | ||
| EnforcedStyle: require_no_parentheses | ||
| Layout/EmptyLineAfterGuardClause: | ||
| Enabled: false | ||
| Lint/UnusedMethodArgument: | ||
| AllowUnusedKeywordArguments: true | ||
| Metrics/ParameterLists: | ||
| Max: 7 | ||
| CountKeywordArgs: false | ||
| # we tend to use @@ and the risk doesn't seem high | ||
| Style/ClassVars: | ||
| Enabled: false | ||
| # | ||
| # Set a relaxed standard on harder-to-address item for ease of | ||
| # contribution - if you are good at refactoring, we welcome PRs to | ||
| # improve existing code! | ||
| # | ||
| Metrics/AbcSize: | ||
| Max: 65 | ||
| Metrics/MethodLength: | ||
| Max: 60 | ||
| Metrics/ClassLength: | ||
| Max: 500 | ||
| Metrics/CyclomaticComplexity: | ||
| Max: 23 | ||
| Metrics/PerceivedComplexity: | ||
| Max: 29 | ||
| RSpec/ExampleLength: | ||
| Max: 17 | ||
| plugins: | ||
| - rubocop-rspec | ||
| - rubocop-rake | ||
| - rubocop-yard |
| # frozen_string_literal: true | ||
| module Solargraph | ||
| class ApiMap | ||
| # Methods for handling constants. | ||
| # | ||
| class Constants | ||
| # @param store [Store] | ||
| def initialize store | ||
| @store = store | ||
| end | ||
| # Resolve a name to a fully qualified namespace or constant. | ||
| # | ||
| # @param name [String] | ||
| # @param gates [Array<Array<String>, String>] | ||
| # @return [String, nil] | ||
| def resolve(name, *gates) | ||
| return store.get_path_pins(name[2..]).first&.path if name.start_with?('::') | ||
| flat = gates.flatten | ||
| flat.push '' if flat.empty? | ||
| cached_resolve[[name, flat]] || resolve_and_cache(name, flat) | ||
| end | ||
| # Get a fully qualified namespace from a reference pin. | ||
| # | ||
| # @param pin [Pin::Reference] | ||
| # @return [String, nil] | ||
| def dereference pin | ||
| resolve(pin.name, pin.reference_gates) | ||
| end | ||
| # Collect a list of all constants defined in the specified gates. | ||
| # | ||
| # @param gates [Array<Array<String>, String>] | ||
| # @return [Array<Pin::Base>] | ||
| def collect(*gates) | ||
| flat = gates.flatten | ||
| cached_collect[flat] || collect_and_cache(flat) | ||
| end | ||
| # Determine fully qualified tag for a given tag used inside the | ||
| # definition of another tag ("context"). This method will start | ||
| # the search in the specified context until it finds a match for | ||
| # the tag. | ||
| # | ||
| # Does not recurse into qualifying the type parameters, but | ||
| # returns any which were passed in unchanged. | ||
| # | ||
| # @param tag [String, nil] The namespace to | ||
| # match, complete with generic parameters set to appropriate | ||
| # values if available | ||
| # @param context_tag [String] The fully qualified context in which | ||
| # the tag was referenced; start from here to resolve the name. | ||
| # Should not be prefixed with '::'. | ||
| # @return [String, nil] fully qualified tag | ||
| def qualify tag, context_tag = '' | ||
| return tag if ['Boolean', 'self', nil].include?(tag) | ||
| type = ComplexType.try_parse(tag) | ||
| return unless type.defined? | ||
| return tag if type.literal? | ||
| context_type = ComplexType.try_parse(context_tag) | ||
| return unless context_type.defined? | ||
| fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace) | ||
| return unless fqns | ||
| fqns + type.substring | ||
| end | ||
| # @return [void] | ||
| def clear | ||
| [cached_collect, cached_resolve].each(&:clear) | ||
| end | ||
| private | ||
| # @return [Store] | ||
| attr_reader :store | ||
| # @param name [String] | ||
| # @param gates [Array<String>] | ||
| # @return [String, nil] | ||
| def resolve_and_cache name, gates | ||
| cached_resolve[[name, gates]] = resolve_uncached(name, gates) | ||
| end | ||
| # @param name [String] | ||
| # @param gates [Array<String>] | ||
| # @return [String, nil] | ||
| def resolve_uncached name, gates | ||
| parts = name.split('::') | ||
| here = parts.shift | ||
| resolved = simple_resolve(here, gates) | ||
| return resolved if parts.empty? || resolved.nil? | ||
| final = "#{resolved}::#{parts.join('::')}".sub(/^::/, '') | ||
| final if store.namespace_exists?(final) | ||
| end | ||
| # @param name [String] | ||
| # @param gates [Array<String>] | ||
| # @return [String, nil] | ||
| def simple_resolve name, gates | ||
| gates.each do |gate| | ||
| here = "#{gate}::#{name}".sub(/^::/, '').sub(/::$/, '') | ||
| return here if store.namespace_exists?(here) | ||
| end | ||
| nil | ||
| end | ||
| # @param gates [Array<String>] | ||
| # @return [Array<Pin::Base>] | ||
| def collect_and_cache gates | ||
| skip = Set.new | ||
| cached_collect[gates] = gates.flat_map do |gate| | ||
| inner_get_constants(gate, %i[public private], skip) | ||
| end | ||
| end | ||
| # @return [Hash{Array(Name, Array<String>) => String, nil}] | ||
| def cached_resolve | ||
| @cached_resolve ||= {} | ||
| end | ||
| # @return [Hash{Array<String> => Array<Pin::Base>}] | ||
| def cached_collect | ||
| @cached_collect ||= {} | ||
| end | ||
| # Determine fully qualified namespace for a given namespace used | ||
| # inside the definition of another tag ("context"). This method | ||
| # will start the search in the specified context until it finds a | ||
| # match for the namespace. | ||
| # | ||
| # @param namespace [String, nil] The namespace to | ||
| # match | ||
| # @param context_namespace [String] The context namespace in which the | ||
| # tag was referenced; start from here to resolve the name | ||
| # @return [String, nil] fully qualified namespace | ||
| def qualify_namespace namespace, context_namespace = '' | ||
| if namespace.start_with?('::') | ||
| inner_qualify(namespace[2..], '', Set.new) | ||
| else | ||
| inner_qualify(namespace, context_namespace, Set.new) | ||
| end | ||
| end | ||
| # @param name [String] Namespace to fully qualify | ||
| # @param root [String] The context to search | ||
| # @param skip [Set<String>] Contexts already searched | ||
| # @return [String, nil] Fully qualified ("rooted") namespace | ||
| def inner_qualify name, root, skip | ||
| return name if name == ComplexType::GENERIC_TAG_NAME | ||
| return nil if name.nil? | ||
| return nil if skip.include?(root) | ||
| skip.add root | ||
| possibles = [] | ||
| if name == '' | ||
| return '' if root == '' | ||
| inner_qualify(root, '', skip) | ||
| else | ||
| return name if root == '' && store.namespace_exists?(name) | ||
| roots = root.to_s.split('::') | ||
| while roots.length.positive? | ||
| fqns = "#{roots.join('::')}::#{name}" | ||
| return fqns if store.namespace_exists?(fqns) | ||
| incs = store.get_includes(roots.join('::')) | ||
| incs.each do |inc| | ||
| foundinc = inner_qualify(name, inc.parametrized_tag.to_s, skip) | ||
| possibles.push foundinc unless foundinc.nil? | ||
| end | ||
| roots.pop | ||
| end | ||
| if possibles.empty? | ||
| incs = store.get_includes('') | ||
| incs.each do |inc| | ||
| foundinc = inner_qualify(name, inc.parametrized_tag.to_s, skip) | ||
| possibles.push foundinc unless foundinc.nil? | ||
| end | ||
| end | ||
| return name if store.namespace_exists?(name) | ||
| possibles.last | ||
| end | ||
| end | ||
| # @param fqns [String] | ||
| # @param visibility [Array<Symbol>] | ||
| # @param skip [Set<String>] | ||
| # @return [Array<Pin::Base>] | ||
| def inner_get_constants fqns, visibility, skip | ||
| return [] if fqns.nil? || skip.include?(fqns) | ||
| skip.add fqns | ||
| result = [] | ||
| store.get_prepends(fqns).each do |pre| | ||
| pre_fqns = resolve(pre.name, pre.closure.gates - skip.to_a) | ||
| result.concat inner_get_constants(pre_fqns, [:public], skip) | ||
| end | ||
| result.concat(store.get_constants(fqns, visibility).sort { |a, b| a.name <=> b.name }) | ||
| store.get_includes(fqns).each do |pin| | ||
| inc_fqns = resolve(pin.name, pin.closure.gates - skip.to_a) | ||
| result.concat inner_get_constants(inc_fqns, [:public], skip) | ||
| end | ||
| sc_ref = store.get_superclass(fqns) | ||
| if sc_ref | ||
| fqsc = dereference(sc_ref) | ||
| result.concat inner_get_constants(fqsc, [:public], skip) unless %w[Object BasicObject].include?(fqsc) | ||
| end | ||
| result | ||
| end | ||
| end | ||
| end | ||
| end |
| # frozen_string_literal: true | ||
| module Solargraph | ||
| module Convention | ||
| # ActiveSupport::Concern is syntactic sugar for a common | ||
| # pattern to include class methods while mixing-in a Module | ||
| # See https://api.rubyonrails.org/classes/ActiveSupport/Concern.html | ||
| class ActiveSupportConcern < Base | ||
| include Logging | ||
| # @return [Array<Pin::Base>] | ||
| attr_reader :pins | ||
| # @param api_map [ApiMap] | ||
| # @param rooted_tag [String] | ||
| # @param scope [Symbol] :class or :instance | ||
| # @param visibility [Array<Symbol>] :public, :protected, and/or :private | ||
| # @param deep [Boolean] whether to include methods from included modules | ||
| # @param skip [Set<String>] | ||
| # @param _no_core [Boolean]n whether to skip core methods | ||
| def object api_map, rooted_tag, scope, visibility, deep, skip, _no_core | ||
| moo = ObjectProcessor.new(api_map, rooted_tag, scope, visibility, deep, skip) | ||
| moo.environ | ||
| end | ||
| # yard-activesupport-concern pulls methods inside | ||
| # 'class_methods' blocks into main class visible from YARD | ||
| # | ||
| # @param _doc_map [DocMap] | ||
| def global _doc_map | ||
| Environ.new(yard_plugins: ['activesupport-concern']) | ||
| end | ||
| # Process an object to add any class methods brought in via | ||
| # ActiveSupport::Concern | ||
| class ObjectProcessor | ||
| include Logging | ||
| attr_reader :environ | ||
| # @param api_map [ApiMap] | ||
| # @param rooted_tag [String] the tag of the class or module being processed | ||
| # @param scope [Symbol] :class or :instance | ||
| # @param visibility [Array<Symbol>] :public, :protected, and/or :private | ||
| # @param deep [Boolean] whether to include methods from included modules | ||
| # @param skip [Set<String>] a set of tags to skip | ||
| def initialize api_map, rooted_tag, scope, visibility, deep, skip | ||
| @api_map = api_map | ||
| @rooted_tag = rooted_tag | ||
| @scope = scope | ||
| @visibility = visibility | ||
| @deep = deep | ||
| @skip = skip | ||
| @environ = Environ.new | ||
| return unless scope == :class | ||
| @rooted_type = ComplexType.parse(rooted_tag).force_rooted | ||
| @fqns = rooted_type.namespace | ||
| @namespace_pin = api_map.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first | ||
| api_map.get_includes(fqns).reverse.each do |include_tag| | ||
| process_include include_tag | ||
| end | ||
| end | ||
| private | ||
| attr_reader :api_map, :rooted_tag, :rooted_type, :scope, | ||
| :visibility, :deep, :skip, :namespace_pin, | ||
| :fqns | ||
| # @param include_tag [Pin::Reference::Include] the include reference pin | ||
| # | ||
| # @return [void] | ||
| def process_include include_tag | ||
| rooted_include_tag = api_map.dereference(include_tag) | ||
| return if rooted_include_tag.nil? | ||
| logger.debug do | ||
| "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ | ||
| "Handling class include include_tag=#{include_tag}" | ||
| end | ||
| module_extends = api_map.get_extends(rooted_include_tag).map(&:parametrized_tag).map(&:to_s) | ||
| logger.debug do | ||
| "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ | ||
| "found module extends of #{rooted_include_tag}: #{module_extends}" | ||
| end | ||
| return unless module_extends.include? 'ActiveSupport::Concern' | ||
| included_class_pins = api_map.inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, | ||
| :class, visibility, deep, skip, true) | ||
| logger.debug do | ||
| "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ | ||
| "Found #{included_class_pins.length} inluded class methods for #{rooted_include_tag}" | ||
| end | ||
| environ.pins.concat included_class_pins | ||
| # another pattern is to put class methods inside a submodule | ||
| classmethods_include_tag = "#{rooted_include_tag}::ClassMethods" | ||
| included_classmethods_pins = | ||
| api_map.inner_get_methods_from_reference(classmethods_include_tag, namespace_pin, rooted_type, | ||
| :instance, visibility, deep, skip, true) | ||
| logger.debug do | ||
| "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ | ||
| "Found #{included_classmethods_pins.length} included classmethod " \ | ||
| "class methods for #{classmethods_include_tag}" | ||
| end | ||
| environ.pins.concat included_classmethods_pins | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
| # frozen_string_literal: true | ||
| require 'open3' | ||
| module Solargraph | ||
| # A workspace consists of the files in a project's directory and the | ||
| # project's configuration. It provides a Source for each file to be used | ||
| # in an associated Library or ApiMap. | ||
| # | ||
| class Workspace | ||
| # Manages determining which gemspecs are available in a workspace | ||
| class RequirePaths | ||
| attr_reader :directory, :config | ||
| # @param directory [String, nil] | ||
| # @param config [Config, nil] | ||
| def initialize directory, config | ||
| @directory = directory | ||
| @config = config | ||
| end | ||
| # Generate require paths from gemspecs if they exist or assume the default | ||
| # lib directory. | ||
| # | ||
| # @return [Array<String>] | ||
| def generate | ||
| result = require_paths_from_gemspec_files | ||
| return configured_require_paths if result.empty? | ||
| result.concat(config.require_paths.map { |p| File.join(directory, p) }) if config | ||
| result | ||
| end | ||
| private | ||
| # @return [Array<String>] | ||
| def require_paths_from_gemspec_files | ||
| results = [] | ||
| gemspec_file_paths.each do |gemspec_file_path| | ||
| results.concat require_path_from_gemspec_file(gemspec_file_path) | ||
| end | ||
| results | ||
| end | ||
| # Get an array of all gemspec files in the workspace. | ||
| # | ||
| # @return [Array<String>] | ||
| def gemspec_file_paths | ||
| return [] if directory.nil? | ||
| @gemspec_file_paths ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs| | ||
| config.nil? || config.allow?(gs) | ||
| end | ||
| end | ||
| # Get additional require paths defined in the configuration. | ||
| # | ||
| # @return [Array<String>] | ||
| def configured_require_paths | ||
| return ['lib'] unless directory | ||
| return [File.join(directory, 'lib')] if !config || config.require_paths.empty? | ||
| config.require_paths.map { |p| File.join(directory, p) } | ||
| end | ||
| # Generate require paths from gemspecs if they exist or assume the default | ||
| # lib directory. | ||
| # | ||
| # @param gemspec_file_path [String] | ||
| # @return [Array<String>] | ||
| def require_path_from_gemspec_file gemspec_file_path | ||
| base = File.dirname(gemspec_file_path) | ||
| # HACK: Evaluating gemspec files violates the goal of not running | ||
| # workspace code, but this is how Gem::Specification.load does it | ||
| # anyway. | ||
| cmd = ['ruby', '-e', | ||
| "require 'rubygems'; " \ | ||
| "require 'json'; " \ | ||
| "spec = eval(File.read('#{gemspec_file_path}'), TOPLEVEL_BINDING, '#{gemspec_file_path}'); " \ | ||
| 'return unless Gem::Specification === spec; ' \ | ||
| 'puts({name: spec.name, paths: spec.require_paths}.to_json)'] | ||
| # @sg-ignore Unresolved call to capture3 on Module<Open3> | ||
| o, e, s = Open3.capture3(*cmd) | ||
| if s.success? | ||
| begin | ||
| hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {} | ||
| return [] if hash.empty? | ||
| hash['paths'].map { |path| File.join(base, path) } | ||
| rescue StandardError => e | ||
| Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}" | ||
| [] | ||
| end | ||
| else | ||
| Solargraph.logger.warn "Error reading #{gemspec_file_path}" | ||
| Solargraph.logger.warn e | ||
| [] | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
| # frozen_string_literal: true | ||
| module Parser | ||
| ## | ||
| # Default AST builder. Uses {AST::Node}s. | ||
| # | ||
| module Builders | ||
| class Default | ||
| ## | ||
| # AST compatibility attribute; since `-> {}` is not semantically | ||
| # equivalent to `lambda {}`, all new code should set this attribute | ||
| # to true. | ||
| # | ||
| # If set to false (the default), `-> {}` is emitted as | ||
| # `s(:block, s(:send, nil, :lambda), s(:args), nil)`. | ||
| # | ||
| # If set to true, `-> {}` is emitted as | ||
| # `s(:block, s(:lambda), s(:args), nil)`. | ||
| # | ||
| # @return [Boolean] | ||
| attr_accessor self.emit_lambda: bool | ||
| ## | ||
| # AST compatibility attribute; block arguments of `m { |a| }` are | ||
| # not semantically equivalent to block arguments of `m { |a,| }` or `m { |a, b| }`, | ||
| # all new code should set this attribute to true. | ||
| # | ||
| # If set to false (the default), arguments of `m { |a| }` are emitted as | ||
| # `s(:args, s(:arg, :a))`. | ||
| # | ||
| # If set to true, arguments of `m { |a| }` are emitted as | ||
| # `s(:args, s(:procarg0, :a)). | ||
| # | ||
| # @return [Boolean] | ||
| attr_accessor self.emit_procarg0: bool | ||
| ## | ||
| # AST compatibility attribute; locations of `__ENCODING__` are not the same | ||
| # as locations of `Encoding::UTF_8` causing problems during rewriting, | ||
| # all new code should set this attribute to true. | ||
| # | ||
| # If set to false (the default), `__ENCODING__` is emitted as | ||
| # ` s(:const, s(:const, nil, :Encoding), :UTF_8)`. | ||
| # | ||
| # If set to true, `__ENCODING__` is emitted as | ||
| # `s(:__ENCODING__)`. | ||
| # | ||
| # @return [Boolean] | ||
| attr_accessor self.emit_encoding: bool | ||
| ## | ||
| # AST compatibility attribute; indexed assignment, `x[] = 1`, is not | ||
| # semantically equivalent to calling the method directly, `x.[]=(1)`. | ||
| # Specifically, in the former case, the expression's value is always 1, | ||
| # and in the latter case, the expression's value is the return value | ||
| # of the `[]=` method. | ||
| # | ||
| # If set to false (the default), `self[1]` is emitted as | ||
| # `s(:send, s(:self), :[], s(:int, 1))`, and `self[1] = 2` is | ||
| # emitted as `s(:send, s(:self), :[]=, s(:int, 1), s(:int, 2))`. | ||
| # | ||
| # If set to true, `self[1]` is emitted as | ||
| # `s(:index, s(:self), s(:int, 1))`, and `self[1] = 2` is | ||
| # emitted as `s(:indexasgn, s(:self), s(:int, 1), s(:int, 2))`. | ||
| # | ||
| # @return [Boolean] | ||
| attr_accessor self.emit_index: bool | ||
| ## | ||
| # AST compatibility attribute; causes a single non-mlhs | ||
| # block argument to be wrapped in s(:procarg0). | ||
| # | ||
| # If set to false (the default), block arguments `|a|` are emitted as | ||
| # `s(:args, s(:procarg0, :a))` | ||
| # | ||
| # If set to true, block arguments `|a|` are emitted as | ||
| # `s(:args, s(:procarg0, s(:arg, :a))` | ||
| # | ||
| # @return [Boolean] | ||
| attr_accessor self.emit_arg_inside_procarg0: bool | ||
| ## | ||
| # AST compatibility attribute; arguments forwarding initially | ||
| # didn't have support for leading arguments | ||
| # (i.e. `def m(a, ...); end` was a syntax error). However, Ruby 3.0 | ||
| # added support for any number of arguments in front of the `...`. | ||
| # | ||
| # If set to false (the default): | ||
| # 1. `def m(...) end` is emitted as | ||
| # s(:def, :m, s(:forward_args), nil) | ||
| # 2. `def m(a, b, ...) end` is emitted as | ||
| # s(:def, :m, | ||
| # s(:args, s(:arg, :a), s(:arg, :b), s(:forward_arg))) | ||
| # | ||
| # If set to true it uses a single format: | ||
| # 1. `def m(...) end` is emitted as | ||
| # s(:def, :m, s(:args, s(:forward_arg))) | ||
| # 2. `def m(a, b, ...) end` is emitted as | ||
| # s(:def, :m, s(:args, s(:arg, :a), s(:arg, :b), s(:forward_arg))) | ||
| # | ||
| # It does't matter that much on 2.7 (because there can't be any leading arguments), | ||
| # but on 3.0 it should be better enabled to use a single AST format. | ||
| # | ||
| # @return [Boolean] | ||
| attr_accessor self.emit_forward_arg: bool | ||
| ## | ||
| # AST compatibility attribute; Starting from Ruby 2.7 keyword arguments | ||
| # of method calls that are passed explicitly as a hash (i.e. with curly braces) | ||
| # are treated as positional arguments and Ruby 2.7 emits a warning on such method | ||
| # call. Ruby 3.0 given an ArgumentError. | ||
| # | ||
| # If set to false (the default) the last hash argument is emitted as `hash`: | ||
| # | ||
| # ``` | ||
| # (send nil :foo | ||
| # (hash | ||
| # (pair | ||
| # (sym :bar) | ||
| # (int 42)))) | ||
| # ``` | ||
| # | ||
| # If set to true it is emitted as `kwargs`: | ||
| # | ||
| # ``` | ||
| # (send nil :foo | ||
| # (kwargs | ||
| # (pair | ||
| # (sym :bar) | ||
| # (int 42)))) | ||
| # ``` | ||
| # | ||
| # Note that `kwargs` node is just a replacement for `hash` argument, | ||
| # so if there's are multiple arguments (or a `kwsplat`) all of them | ||
| # are wrapped into `kwargs` instead of `hash`: | ||
| # | ||
| # ``` | ||
| # (send nil :foo | ||
| # (kwargs | ||
| # (pair | ||
| # (sym :a) | ||
| # (int 42)) | ||
| # (kwsplat | ||
| # (send nil :b)) | ||
| # (pair | ||
| # (sym :c) | ||
| # (int 10)))) | ||
| # ``` | ||
| attr_accessor self.emit_kwargs: bool | ||
| ## | ||
| # AST compatibility attribute; Starting from 3.0 Ruby returns | ||
| # true/false from single-line pattern matching with `in` keyword. | ||
| # | ||
| # Before 3.0 there was an exception if given value doesn't match pattern. | ||
| # | ||
| # NOTE: This attribute affects only Ruby 2.7 grammar. | ||
| # 3.0 grammar always emits `match_pattern`/`match_pattern_p` | ||
| # | ||
| # If compatibility attribute set to false `foo in bar` is emitted as `in_match`: | ||
| # | ||
| # ``` | ||
| # (in-match | ||
| # (send nil :foo) | ||
| # (match-var :bar)) | ||
| # ``` | ||
| # | ||
| # If set to true it's emitted as `match_pattern_p`: | ||
| # ``` | ||
| # (match-pattern-p | ||
| # (send nil :foo) | ||
| # (match-var :bar)) | ||
| # ``` | ||
| attr_accessor self.emit_match_pattern: bool | ||
| ## | ||
| # If set to true (the default), `__FILE__` and `__LINE__` are transformed to | ||
| # literal nodes. For example, `s(:str, "lib/foo.rb")` and `s(:int, 10)`. | ||
| # | ||
| # If set to false, `__FILE__` and `__LINE__` are emitted as-is, | ||
| # i.e. as `s(:__FILE__)` and `s(:__LINE__)` nodes. | ||
| # | ||
| # Source maps are identical in both cases. | ||
| # | ||
| # @return [Boolean] | ||
| attr_accessor emit_file_line_as_literals: bool | ||
| def value: (untyped token) -> untyped | ||
| def string_value: (untyped token) -> String | ||
| def loc: (untyped token) -> untyped | ||
| end | ||
| end | ||
| end |
| --- | ||
| name: thor | ||
| version: '1.2' | ||
| source: | ||
| type: git | ||
| name: ruby/gem_rbs_collection | ||
| revision: 98541aabafdf403b16ebae6fe4060d18bee75e93 | ||
| remote: https://github.com/ruby/gem_rbs_collection.git | ||
| repo_dir: gems |
| # manifest.yaml describes dependencies which do not appear in the gemspec. | ||
| # If this gem includes such dependencies, comment-out the following lines and | ||
| # declare the dependencies. | ||
| # If all dependencies appear in the gemspec, you should remove this file. | ||
| # | ||
| # dependencies: | ||
| # - name: pathname |
| class Thor | ||
| class Group | ||
| end | ||
| module Actions | ||
| class CreateFile | ||
| end | ||
| def create_file: (String destination, String data, ?verbose: bool) -> String | ||
| | (String destination, ?verbose: bool) { () -> String } -> String | ||
| end | ||
| class Error | ||
| end | ||
| def self.start: (Array[String] given_args, ?Hash[Symbol, untyped] config) -> void | ||
| end |
@@ -1,8 +0,13 @@ | ||
| name: Plugin Backwards Compatibility Tests | ||
| --- | ||
| name: Plugin | ||
| # To debug locally: | ||
| # npm install -g act | ||
| # cd /Users/broz/src/solargraph/ && act pull_request -j run_solargraph_rails_specs # e.g. | ||
| on: | ||
| push: | ||
| branches: [ master ] | ||
| branches: [master] | ||
| pull_request: | ||
| branches: [ master ] | ||
| branches: [master] | ||
@@ -13,3 +18,3 @@ permissions: | ||
| jobs: | ||
| test: | ||
| regression: | ||
| runs-on: ubuntu-latest | ||
@@ -23,3 +28,3 @@ | ||
| ruby-version: '3.0' | ||
| bundler-cache: false | ||
| bundler-cache: true | ||
| - uses: awalsh128/cache-apt-pkgs-action@latest | ||
@@ -34,2 +39,3 @@ with: | ||
| bundle install | ||
| bundle update rbs | ||
| - name: Configure to use plugins | ||
@@ -41,3 +47,3 @@ run: | | ||
| - name: Install gem types | ||
| run: bundle exec rbs collection install | ||
| run: bundle exec rbs collection update | ||
| - name: Ensure typechecking still works | ||
@@ -47,1 +53,137 @@ run: bundle exec solargraph typecheck --level typed | ||
| run: bundle exec rake spec | ||
| rails: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| - name: Set up Ruby | ||
| uses: ruby/setup-ruby@v1 | ||
| with: | ||
| ruby-version: '3.0' | ||
| bundler-cache: false | ||
| - uses: awalsh128/cache-apt-pkgs-action@latest | ||
| with: | ||
| packages: yq | ||
| version: 1.0 | ||
| - name: Install gems | ||
| run: | | ||
| echo 'gem "solargraph-rails"' > .Gemfile | ||
| bundle install | ||
| bundle update rbs | ||
| - name: Configure to use plugins | ||
| run: | | ||
| bundle exec solargraph config | ||
| yq -yi '.plugins += ["solargraph-rails"]' .solargraph.yml | ||
| - name: Install gem types | ||
| run: bundle exec rbs collection update | ||
| - name: Ensure typechecking still works | ||
| run: bundle exec solargraph typecheck --level typed | ||
| - name: Ensure specs still run | ||
| run: bundle exec rake spec | ||
| rspec: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| - name: Set up Ruby | ||
| uses: ruby/setup-ruby@v1 | ||
| with: | ||
| ruby-version: '3.0' | ||
| bundler-cache: false | ||
| - uses: awalsh128/cache-apt-pkgs-action@latest | ||
| with: | ||
| packages: yq | ||
| version: 1.0 | ||
| - name: Install gems | ||
| run: | | ||
| echo 'gem "solargraph-rspec"' >> .Gemfile | ||
| bundle install | ||
| bundle update rbs | ||
| - name: Configure to use plugins | ||
| run: | | ||
| bundle exec solargraph config | ||
| yq -yi '.plugins += ["solargraph-rspec"]' .solargraph.yml | ||
| - name: Install gem types | ||
| run: bundle exec rbs collection update | ||
| - name: Ensure typechecking still works | ||
| run: bundle exec solargraph typecheck --level typed | ||
| - name: Ensure specs still run | ||
| run: bundle exec rake spec | ||
| # run_solargraph_rspec_specs: | ||
| # # check out solargraph-rspec as well as this project, and point the former to use the latter as a local gem | ||
| # runs-on: ubuntu-latest | ||
| # steps: | ||
| # - uses: actions/checkout@v3 | ||
| # - name: clone https://github.com/lekemula/solargraph-rspec/ | ||
| # run: | | ||
| # cd .. | ||
| # git clone https://github.com/lekemula/solargraph-rspec.git | ||
| # cd solargraph-rspec | ||
| # - name: Set up Ruby | ||
| # uses: ruby/setup-ruby@v1 | ||
| # with: | ||
| # ruby-version: '3.0' | ||
| # bundler-cache: false | ||
| # - name: Install gems | ||
| # run: | | ||
| # cd ../solargraph-rspec | ||
| # echo "gem 'solargraph', path: '../solargraph'" >> Gemfile | ||
| # bundle install | ||
| # - name: Run specs | ||
| # run: | | ||
| # cd ../solargraph-rspec | ||
| # bundle exec rake spec | ||
| run_solargraph_rails_specs: | ||
| # check out solargraph-rails as well as this project, and point the former to use the latter as a local gem | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| - name: clone solargraph-rails | ||
| run: | | ||
| cd .. | ||
| git clone https://github.com/iftheshoefritz/solargraph-rails.git | ||
| cd solargraph-rails | ||
| - name: Set up Ruby | ||
| uses: ruby/setup-ruby@v1 | ||
| with: | ||
| # solargraph-rails supports Ruby 3.0+ | ||
| ruby-version: '3.0' | ||
| bundler-cache: false | ||
| bundler: latest | ||
| env: | ||
| MATRIX_RAILS_VERSION: "7.0" | ||
| - name: Install gems | ||
| run: | | ||
| set -x | ||
| BUNDLE_PATH="${GITHUB_WORKSPACE:?}/vendor/bundle" | ||
| export BUNDLE_PATH | ||
| cd ../solargraph-rails | ||
| echo "gem 'solargraph', path: '${GITHUB_WORKSPACE:?}'" >> Gemfile | ||
| bundle install | ||
| bundle update rbs | ||
| RAILS_DIR="$(pwd)/spec/rails7" | ||
| export RAILS_DIR | ||
| cd ${RAILS_DIR} | ||
| bundle install | ||
| bundle exec --gemfile ../../Gemfile rbs --version | ||
| bundle exec --gemfile ../../Gemfile rbs collection install | ||
| cd ../../ | ||
| # bundle exec rbs collection init | ||
| # bundle exec rbs collection install | ||
| env: | ||
| MATRIX_RAILS_VERSION: "7.0" | ||
| MATRIX_RAILS_MAJOR_VERSION: '7' | ||
| - name: Run specs | ||
| run: | | ||
| BUNDLE_PATH="${GITHUB_WORKSPACE:?}/vendor/bundle" | ||
| export BUNDLE_PATH | ||
| cd ../solargraph-rails | ||
| bundle exec solargraph --version | ||
| bundle info solargraph | ||
| bundle info rbs | ||
| bundle info yard | ||
| ALLOW_IMPROVEMENTS=true bundle exec rake spec | ||
| env: | ||
| MATRIX_RAILS_VERSION: "7.0" |
@@ -20,4 +20,3 @@ # This workflow uses actions that are not certified by GitHub. | ||
| jobs: | ||
| test: | ||
| rspec: | ||
| runs-on: ubuntu-latest | ||
@@ -27,3 +26,9 @@ strategy: | ||
| ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4', 'head'] | ||
| rbs-version: ['3.6.1', '3.9.4', '4.0.0.dev.4'] | ||
| # Ruby 3.0 doesn't work with RBS 3.9.4 or 4.0.0.dev.4 | ||
| exclude: | ||
| - ruby-version: '3.0' | ||
| rbs-version: '3.9.4' | ||
| - ruby-version: '3.0' | ||
| rbs-version: '4.0.0.dev.4' | ||
| steps: | ||
@@ -36,5 +41,35 @@ - uses: actions/checkout@v3 | ||
| bundler-cache: false | ||
| - 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: | ||
| # warning: tsort was loaded from the standard library, | ||
| # but will no longer be part of the default gems | ||
| # starting from Ruby 3.6.0 | ||
| - name: Work around legacy rbs deprecation on ruby > 3.4 | ||
| run: echo "gem 'tsort'" >> .Gemfile | ||
| - name: Install gems | ||
| run: | | ||
| bundle install | ||
| bundle update rbs # use latest available for this Ruby version | ||
| - name: Run tests | ||
| run: bundle exec rake spec | ||
| undercover: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| with: | ||
| # fetch all history for all branches and tags so we can | ||
| # compare against origin/master | ||
| fetch-depth: 0 | ||
| - name: Set up Ruby | ||
| uses: ruby/setup-ruby@v1 | ||
| with: | ||
| ruby-version: '3.4' | ||
| bundler-cache: false | ||
| - name: Install gems | ||
| run: bundle install | ||
| - name: Run tests | ||
| run: bundle exec rspec | ||
| run: bundle exec rake spec | ||
| - name: Check PR coverage | ||
| run: bundle exec rake undercover | ||
| continue-on-error: true |
@@ -20,3 +20,4 @@ # This workflow uses actions that are not certified by GitHub. | ||
| jobs: | ||
| test: | ||
| solargraph_typed: | ||
| name: Solargraph / typed | ||
@@ -33,3 +34,5 @@ runs-on: ubuntu-latest | ||
| - name: Install gems | ||
| run: bundle install | ||
| run: | | ||
| bundle install | ||
| bundle update rbs # use latest available for this Ruby version | ||
| - name: Install gem types | ||
@@ -36,0 +39,0 @@ run: bundle exec rbs collection install |
+5
-0
@@ -12,1 +12,6 @@ /.gem_rbs_collection | ||
| /tmp/ | ||
| /rspec-examples.txt | ||
| /.ruby-version | ||
| /Makefile | ||
| /.pryrc | ||
| /.rspec-local |
+42
-0
@@ -0,1 +1,43 @@ | ||
| ## 0.57.0 - September 16, 2025 | ||
| - Support ActiveSupport::Concern pattern for class methods (#948) | ||
| - More CI checks (#996) | ||
| - Linting / type annotation fixes (#999) | ||
| - Avoid overlapping chdir calls in multiple threads (#1007) | ||
| - Fix kwarg generation in ApiMap::SourceToYard (#1003) | ||
| - Enable Steep typechecking of Solargraph code (#1004) | ||
| - Fix convention requires (#1008) | ||
| - Plugin Util: Add Combination Priority (#1010) | ||
| - [regression] Fix crash while typechecking files with Struct use (#1031) | ||
| - Remove yard reference from gemfile (#1033) | ||
| - Allow newer RBS gem versions, exclude incompatible ones (#995) | ||
| - Look for external requires before cataloging bench (#1021) | ||
| - Remove Library#folding_ranges (#904) | ||
| - Complain in strong type-checking if an @sg-ignore line is not needed (#1011) | ||
| - Document a log level env variable (#894) | ||
| - Fix hole in type checking evaluation (#1009) | ||
| - Improve typechecking error message (#1014) | ||
| - Internal strict type-checking fixes (#1013) | ||
| - Reproduce and fix a ||= (or-asgn) evaluation issue (#1017) | ||
| - Define closure for Pin::Symbol, for completeness (#1027) | ||
| - Fix 'all!' config to reporters (#1018) | ||
| - Fix DocMap.all_rbs_collection_gems_in_memory return type (#1037) | ||
| - Fix RuboCop linting errors in regular expressions (#1038) | ||
| - Resolve class aliases via Constant pins (#1029) | ||
| - Speed-up LSP completion response times (#1035) | ||
| - Revert "Resolve class aliases via Constant pins (#1029)" (#1041) | ||
| - Avoid stack errors when resolving method aliases (#1040) | ||
| - [regression] Refine order of object convention method pins (#1036) | ||
| - Fix crash while generating activesupport pins (#1043) | ||
| - Type annotation improvements (#1016) | ||
| - Resolve class aliases via Constant pins (#1048) | ||
| - Understand "Parser::AST::Node < AST::Node" in RBS (#1060) | ||
| - Factor out require_paths logic to its own class (#1062) | ||
| - Fix type errors found in strong typechecking (#1045) | ||
| - Run plugin specs separately for perf insights (#1046) | ||
| - Run specs from solargraph-rails configured against current code in CI (#892) | ||
| - Fix Convention/Plugin pins not being updated on file change (#1028) | ||
| - Fix solargraph-rails check (#1073) | ||
| - Flow sensitive typing handles x.is_a? without an argument (#1070) | ||
| - Refactor reference pin handling (#1058) | ||
| ## 0.56.2 - July 29, 2025 | ||
@@ -2,0 +44,0 @@ - Add support for Ruby Data.define (#970) |
@@ -56,2 +56,4 @@ # frozen_string_literal: true | ||
| CHDIR_MUTEX = Mutex.new | ||
| # @param type [Symbol] Type of assert. | ||
@@ -58,0 +60,0 @@ def self.asserts_on?(type) |
+212
-234
@@ -16,2 +16,3 @@ # frozen_string_literal: true | ||
| autoload :Index, 'solargraph/api_map/index' | ||
| autoload :Constants, 'solargraph/api_map/constants' | ||
@@ -30,3 +31,2 @@ # @return [Array<String>] | ||
| @cache = Cache.new | ||
| @method_alias_stack = [] | ||
| index pins | ||
@@ -41,2 +41,3 @@ end | ||
| # @param other [Object] | ||
| def eql?(other) | ||
@@ -47,2 +48,3 @@ self.class == other.class && | ||
| # @param other [Object] | ||
| def ==(other) | ||
@@ -71,3 +73,3 @@ self.eql?(other) | ||
| @source_map_hash = {} | ||
| implicit.clear | ||
| conventions_environ.clear | ||
| cache.clear | ||
@@ -81,6 +83,7 @@ store.update @@core_map.pins, pins | ||
| # @param source [Source] | ||
| # @param live [Boolean] True for live source map (active editor file) | ||
| # @return [self] | ||
| def map source | ||
| def map source, live: false | ||
| map = Solargraph::SourceMap.map(source) | ||
| catalog Bench.new(source_maps: [map]) | ||
| catalog Bench.new(source_maps: [map], live_map: live ? map : nil) | ||
| self | ||
@@ -96,8 +99,8 @@ end | ||
| iced_pins = bench.icebox.flat_map(&:pins) | ||
| live_pins = bench.live_map&.pins || [] | ||
| implicit.clear | ||
| live_pins = bench.live_map&.all_pins || [] | ||
| conventions_environ.clear | ||
| source_map_hash.each_value do |map| | ||
| implicit.merge map.environ | ||
| conventions_environ.merge map.conventions_environ | ||
| end | ||
| unresolved_requires = (bench.external_requires + implicit.requires + bench.workspace.config.required).to_a.compact.uniq | ||
| unresolved_requires = (bench.external_requires + conventions_environ.requires + bench.workspace.config.required).to_a.compact.uniq | ||
| recreate_docmap = @unresolved_requires != unresolved_requires || | ||
@@ -111,3 +114,3 @@ @doc_map&.uncached_yard_gemspecs&.any? || | ||
| end | ||
| @cache.clear if store.update(@@core_map.pins, @doc_map.pins, implicit.pins, iced_pins, live_pins) | ||
| @cache.clear if store.update(@@core_map.pins, @doc_map.pins, conventions_environ.pins, iced_pins, live_pins) | ||
| @missing_docs = [] # @todo Implement missing docs | ||
@@ -121,5 +124,6 @@ self | ||
| protected def equality_fields | ||
| [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires] | ||
| [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires] | ||
| end | ||
| # @return [DocMap] | ||
| def doc_map | ||
@@ -144,3 +148,3 @@ @doc_map ||= DocMap.new([], []) | ||
| # @return [Array<Pin::Base>] | ||
| # @return [Enumerable<Pin::Base>] | ||
| def core_pins | ||
@@ -162,4 +166,4 @@ @@core_map.pins | ||
| # @return [Environ] | ||
| def implicit | ||
| @implicit ||= Environ.new | ||
| def conventions_environ | ||
| @conventions_environ ||= Environ.new | ||
| end | ||
@@ -200,2 +204,4 @@ | ||
| # @param out [IO, nil] | ||
| # @return [void] | ||
| def cache_all!(out) | ||
@@ -205,2 +211,6 @@ @doc_map.cache_all!(out) | ||
| # @param gemspec [Gem::Specification] | ||
| # @param rebuild [Boolean] | ||
| # @param out [IO, nil] | ||
| # @return [void] | ||
| def cache_gem(gemspec, rebuild: false, out: nil) | ||
@@ -218,5 +228,2 @@ @doc_map.cache(gemspec, rebuild: rebuild, out: out) | ||
| # | ||
| # @todo IO::NULL is incorrectly inferred to be a String. | ||
| # @sg-ignore | ||
| # | ||
| # @param directory [String] | ||
@@ -272,15 +279,9 @@ # @param out [IO] The output stream for messages | ||
| namespace ||= '' | ||
| contexts.push '' if contexts.empty? | ||
| cached = cache.get_constants(namespace, contexts) | ||
| return cached.clone unless cached.nil? | ||
| skip = Set.new | ||
| result = [] | ||
| contexts.each do |context| | ||
| fqns = qualify(namespace, context) | ||
| visibility = [:public] | ||
| visibility.push :private if fqns == context | ||
| result.concat inner_get_constants(fqns, visibility, skip) | ||
| end | ||
| cache.set_constants(namespace, contexts, result) | ||
| result | ||
| gates = contexts.clone | ||
| gates.push '' if contexts.empty? && namespace.empty? | ||
| gates.push namespace unless namespace.empty? | ||
| store.constants | ||
| .collect(gates) | ||
| .select { |pin| namespace.empty? || contexts.empty? || pin.namespace == namespace } | ||
| .select { |pin| pin.visibility == :public || pin.namespace == namespace } | ||
| end | ||
@@ -311,40 +312,23 @@ | ||
| def qualify tag, context_tag = '' | ||
| return tag if ['Boolean', 'self', nil].include?(tag) | ||
| store.constants.qualify(tag, context_tag) | ||
| end | ||
| context_type = ComplexType.try_parse(context_tag).force_rooted | ||
| return unless context_type | ||
| # Get a fully qualified namespace from a reference pin. | ||
| # | ||
| # @param pin [Pin::Reference] | ||
| # @return [String, nil] | ||
| def dereference(pin) | ||
| store.constants.dereference(pin) | ||
| end | ||
| type = ComplexType.try_parse(tag) | ||
| return unless type | ||
| return tag if type.literal? | ||
| context_type = ComplexType.try_parse(context_tag) | ||
| return unless context_type | ||
| fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace) | ||
| return unless fqns | ||
| fqns + type.substring | ||
| # @param fqns [String] | ||
| # @return [Array<String>] | ||
| def get_extends(fqns) | ||
| store.get_extends(fqns) | ||
| end | ||
| # Determine fully qualified namespace for a given namespace used | ||
| # inside the definition of another tag ("context"). This method | ||
| # will start the search in the specified context until it finds a | ||
| # match for the namespace. | ||
| # | ||
| # @param namespace [String, nil] The namespace to | ||
| # match | ||
| # @param context_namespace [String] The context namespace in which the | ||
| # tag was referenced; start from here to resolve the name | ||
| # @return [String, nil] fully qualified namespace | ||
| def qualify_namespace(namespace, context_namespace = '') | ||
| cached = cache.get_qualified_namespace(namespace, context_namespace) | ||
| return cached.clone unless cached.nil? | ||
| result = if namespace.start_with?('::') | ||
| inner_qualify(namespace[2..-1], '', Set.new) | ||
| else | ||
| inner_qualify(namespace, context_namespace, Set.new) | ||
| end | ||
| cache.set_qualified_namespace(namespace, context_namespace, result) | ||
| result | ||
| # @param fqns [String] | ||
| # @return [Array<String>] | ||
| def get_includes(fqns) | ||
| store.get_includes(fqns) | ||
| end | ||
@@ -362,7 +346,6 @@ | ||
| result.concat store.get_instance_variables(namespace, scope) | ||
| sc = qualify_lower(store.get_superclass(namespace), namespace) | ||
| until sc.nil? || used.include?(sc) | ||
| used.push sc | ||
| result.concat store.get_instance_variables(sc, scope) | ||
| sc = qualify_lower(store.get_superclass(sc), sc) | ||
| sc_fqns = namespace | ||
| while (sc = store.get_superclass(sc_fqns)) | ||
| sc_fqns = store.constants.dereference(sc) | ||
| result.concat store.get_instance_variables(sc_fqns, scope) | ||
| end | ||
@@ -372,2 +355,3 @@ result | ||
| # @sg-ignore Missing @return tag for Solargraph::ApiMap#visible_pins | ||
| # @see Solargraph::Parser::FlowSensitiveTyping#visible_pins | ||
@@ -424,3 +408,3 @@ def visible_pins(*args, **kwargs, &blk) | ||
| # @todo Implement domains | ||
| implicit.domains.each do |domain| | ||
| conventions_environ.domains.each do |domain| | ||
| type = ComplexType.try_parse(domain) | ||
@@ -536,2 +520,4 @@ next if type.undefined? | ||
| # @param scope [Symbol] :instance or :class | ||
| # @param visibility [Array<Symbol>] :public, :protected, and/or :private | ||
| # @param preserve_generics [Boolean] | ||
| # @return [Array<Solargraph::Pin::Method>] | ||
@@ -541,4 +527,14 @@ def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false | ||
| fqns = rooted_type.namespace | ||
| namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first | ||
| methods = get_methods(rooted_tag, scope: scope, visibility: visibility).select { |p| p.name == name } | ||
| namespace_pin = store.get_path_pins(fqns).first | ||
| methods = if namespace_pin.is_a?(Pin::Constant) | ||
| type = namespace_pin.infer(self) | ||
| if type.defined? | ||
| namespace_pin = store.get_path_pins(type.namespace).first | ||
| get_methods(type.namespace, scope: scope, visibility: visibility).select { |p| p.name == name } | ||
| else | ||
| [] | ||
| end | ||
| else | ||
| get_methods(rooted_tag, scope: scope, visibility: visibility).select { |p| p.name == name } | ||
| end | ||
| methods = erase_generics(namespace_pin, rooted_type, methods) unless preserve_generics | ||
@@ -653,9 +649,18 @@ methods | ||
| def super_and_sub?(sup, sub) | ||
| fqsup = qualify(sup) | ||
| cls = qualify(sub) | ||
| tested = [] | ||
| until fqsup.nil? || cls.nil? || tested.include?(cls) | ||
| return true if cls == fqsup | ||
| tested.push cls | ||
| cls = qualify_superclass(cls) | ||
| sup = ComplexType.try_parse(sup) | ||
| sub = ComplexType.try_parse(sub) | ||
| # @todo If two literals are different values of the same type, it would | ||
| # make more sense for super_and_sub? to return true, but there are a | ||
| # few callers that currently expect this to be false. | ||
| return false if sup.literal? && sub.literal? && sup.to_s != sub.to_s | ||
| sup = sup.simplify_literals.to_s | ||
| sub = sub.simplify_literals.to_s | ||
| return true if sup == sub | ||
| sc_fqns = sub | ||
| while (sc = store.get_superclass(sc_fqns)) | ||
| sc_new = store.constants.dereference(sc) | ||
| # Cyclical inheritance is invalid | ||
| return false if sc_new == sc_fqns | ||
| sc_fqns = sc_new | ||
| return true if sc_fqns == sup | ||
| end | ||
@@ -673,3 +678,3 @@ false | ||
| def type_include?(host_ns, module_ns) | ||
| store.get_includes(host_ns).map { |inc_tag| ComplexType.parse(inc_tag).name }.include?(module_ns) | ||
| store.get_includes(host_ns).map { |inc_tag| inc_tag.parametrized_tag.name }.include?(module_ns) | ||
| end | ||
@@ -682,2 +687,3 @@ | ||
| with_resolved_aliases = pins.map do |pin| | ||
| next pin unless pin.is_a?(Pin::MethodAlias) | ||
| resolved = resolve_method_alias(pin) | ||
@@ -691,2 +697,37 @@ next nil if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility) | ||
| # @param fq_reference_tag [String] A fully qualified whose method should be pulled in | ||
| # @param namespace_pin [Pin::Base] Namespace pin for the rooted_type | ||
| # parameter - used to pull generics information | ||
| # @param type [ComplexType] The type which is having its | ||
| # methods supplemented from fq_reference_tag | ||
| # @param scope [Symbol] :class or :instance | ||
| # @param visibility [Array<Symbol>] :public, :protected, and/or :private | ||
| # @param deep [Boolean] | ||
| # @param skip [Set<String>] | ||
| # @param no_core [Boolean] Skip core classes if true | ||
| # @return [Array<Pin::Base>] | ||
| 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 | ||
| # type are relative to the generic values passed in the | ||
| # reference. e.g., Foo<String> might include Enumerable<String> | ||
| # | ||
| # @todo perform the same translation in the other areas | ||
| # here after adding a spec and handling things correctly | ||
| # in ApiMap::Store and RbsMap::Conversions for each | ||
| resolved_reference_type = ComplexType.parse(fq_reference_tag).force_rooted.resolve_generics(namespace_pin, type) | ||
| # @todo Can inner_get_methods be cached? Lots of lookups of base types going on. | ||
| methods = inner_get_methods(resolved_reference_type.tag, scope, visibility, deep, skip, no_core) | ||
| if namespace_pin && !resolved_reference_type.all_params.empty? | ||
| reference_pin = store.get_path_pins(resolved_reference_type.name).select { |p| p.is_a?(Pin::Namespace) }.first | ||
| # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolving generics with #{reference_pin.generics}, #{resolved_reference_type.rooted_tags}" } | ||
| methods = methods.map do |method_pin| | ||
| method_pin.resolve_generics(reference_pin, resolved_reference_type) | ||
| end | ||
| end | ||
| # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolved_reference_type: #{resolved_reference_type} for type=#{type}: #{methods.map(&:name)}" } | ||
| methods | ||
| end | ||
| private | ||
@@ -715,2 +756,3 @@ | ||
| # @return [Array<Pin::Base>] | ||
| # rubocop:disable Metrics/CyclomaticComplexity | ||
| def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false | ||
@@ -726,5 +768,12 @@ rooted_type = ComplexType.parse(rooted_tag).force_rooted | ||
| result = [] | ||
| environ = Convention.for_object(self, rooted_tag, scope, visibility, deep, skip, no_core) | ||
| # 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 } | ||
| result.concat direct_convention_methods | ||
| if deep && scope == :instance | ||
| store.get_prepends(fqns).reverse.each do |im| | ||
| fqim = qualify(im, fqns) | ||
| fqim = store.constants.dereference(im) | ||
| result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil? | ||
@@ -737,8 +786,20 @@ end | ||
| 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}" } | ||
| result.concat methods | ||
| if deep | ||
| result.concat convention_methods_by_reference | ||
| if scope == :instance | ||
| store.get_includes(fqns).reverse.each do |include_tag| | ||
| rooted_include_tag = qualify(include_tag, rooted_tag) | ||
| result.concat inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true) | ||
| store.get_includes(fqns).reverse.each do |ref| | ||
| const = get_constants('', *ref.closure.gates).find { |pin| pin.path.end_with? ref.name } | ||
| if const.is_a?(Pin::Namespace) | ||
| result.concat inner_get_methods(const.path, scope, visibility, deep, skip, true) | ||
| elsif const.is_a?(Pin::Constant) | ||
| type = const.infer(self) | ||
| result.concat inner_get_methods(type.namespace, scope, visibility, deep, skip, true) if type.defined? | ||
| else | ||
| referenced_tag = ref.parametrized_tag | ||
| next unless referenced_tag.defined? | ||
| result.concat inner_get_methods_from_reference(referenced_tag.to_s, namespace_pin, rooted_type, scope, visibility, deep, skip, true) | ||
| end | ||
| end | ||
@@ -750,4 +811,5 @@ rooted_sc_tag = qualify_superclass(rooted_tag) | ||
| else | ||
| logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" } | ||
| store.get_extends(fqns).reverse.each do |em| | ||
| fqem = qualify(em, fqns) | ||
| fqem = store.constants.dereference(em) | ||
| result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil? | ||
@@ -772,61 +834,4 @@ end | ||
| end | ||
| # rubocop:enable Metrics/CyclomaticComplexity | ||
| # @param fq_reference_tag [String] A fully qualified whose method should be pulled in | ||
| # @param namespace_pin [Pin::Base] Namespace pin for the rooted_type | ||
| # parameter - used to pull generics information | ||
| # @param type [ComplexType] The type which is having its | ||
| # methods supplemented from fq_reference_tag | ||
| # @param scope [Symbol] :class or :instance | ||
| # @param visibility [Array<Symbol>] :public, :protected, and/or :private | ||
| # @param deep [Boolean] | ||
| # @param skip [Set<String>] | ||
| # @param no_core [Boolean] Skip core classes if true | ||
| # @return [Array<Pin::Base>] | ||
| 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 | ||
| # type are relative to the generic values passed in the | ||
| # reference. e.g., Foo<String> might include Enumerable<String> | ||
| # | ||
| # @todo perform the same translation in the other areas | ||
| # here after adding a spec and handling things correctly | ||
| # in ApiMap::Store and RbsMap::Conversions for each | ||
| resolved_reference_type = ComplexType.parse(fq_reference_tag).force_rooted.resolve_generics(namespace_pin, type) | ||
| # @todo Can inner_get_methods be cached? Lots of lookups of base types going on. | ||
| methods = inner_get_methods(resolved_reference_type.tag, scope, visibility, deep, skip, no_core) | ||
| if namespace_pin && !resolved_reference_type.all_params.empty? | ||
| reference_pin = store.get_path_pins(resolved_reference_type.name).select { |p| p.is_a?(Pin::Namespace) }.first | ||
| # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolving generics with #{reference_pin.generics}, #{resolved_reference_type.rooted_tags}" } | ||
| methods = methods.map do |method_pin| | ||
| method_pin.resolve_generics(reference_pin, resolved_reference_type) | ||
| end | ||
| end | ||
| # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolved_reference_type: #{resolved_reference_type} for type=#{type}: #{methods.map(&:name)}" } | ||
| methods | ||
| end | ||
| # @param fqns [String] | ||
| # @param visibility [Array<Symbol>] | ||
| # @param skip [Set<String>] | ||
| # @return [Array<Pin::Base>] | ||
| def inner_get_constants fqns, visibility, skip | ||
| return [] if fqns.nil? || skip.include?(fqns) | ||
| skip.add fqns | ||
| result = [] | ||
| store.get_prepends(fqns).each do |is| | ||
| result.concat inner_get_constants(qualify(is, fqns), [:public], skip) | ||
| end | ||
| result.concat store.get_constants(fqns, visibility) | ||
| .sort { |a, b| a.name <=> b.name } | ||
| store.get_includes(fqns).each do |is| | ||
| result.concat inner_get_constants(qualify(is, fqns), [:public], skip) | ||
| end | ||
| fqsc = qualify_superclass(fqns) | ||
| unless %w[Object BasicObject].include?(fqsc) | ||
| result.concat inner_get_constants(fqsc, [:public], skip) | ||
| end | ||
| result | ||
| end | ||
| # @return [Hash] | ||
@@ -837,65 +842,8 @@ def path_macros | ||
| # @param namespace [String] | ||
| # @param context [String] | ||
| # @param fq_sub_tag [String] | ||
| # @return [String, nil] | ||
| def qualify_lower namespace, context | ||
| qualify namespace, context.split('::')[0..-2].join('::') | ||
| end | ||
| # @param fq_tag [String] | ||
| # @return [String, nil] | ||
| def qualify_superclass fq_sub_tag | ||
| fq_sub_type = ComplexType.try_parse(fq_sub_tag) | ||
| fq_sub_ns = fq_sub_type.name | ||
| sup_tag = store.get_superclass(fq_sub_tag) | ||
| sup_type = ComplexType.try_parse(sup_tag) | ||
| sup_ns = sup_type.name | ||
| return nil if sup_tag.nil? | ||
| parts = fq_sub_ns.split('::') | ||
| last = parts.pop | ||
| parts.pop if last == sup_ns | ||
| qualify(sup_tag, parts.join('::')) | ||
| store.qualify_superclass fq_sub_tag | ||
| end | ||
| # @param name [String] Namespace to fully qualify | ||
| # @param root [String] The context to search | ||
| # @param skip [Set<String>] Contexts already searched | ||
| # @return [String, nil] Fully qualified ("rooted") namespace | ||
| def inner_qualify name, root, skip | ||
| return name if name == ComplexType::GENERIC_TAG_NAME | ||
| return nil if name.nil? | ||
| return nil if skip.include?(root) | ||
| skip.add root | ||
| possibles = [] | ||
| if name == '' | ||
| if root == '' | ||
| return '' | ||
| else | ||
| return inner_qualify(root, '', skip) | ||
| end | ||
| else | ||
| return name if root == '' && store.namespace_exists?(name) | ||
| roots = root.to_s.split('::') | ||
| while roots.length > 0 | ||
| fqns = roots.join('::') + '::' + name | ||
| return fqns if store.namespace_exists?(fqns) | ||
| incs = store.get_includes(roots.join('::')) | ||
| incs.each do |inc| | ||
| foundinc = inner_qualify(name, inc, skip) | ||
| possibles.push foundinc unless foundinc.nil? | ||
| end | ||
| roots.pop | ||
| end | ||
| if possibles.empty? | ||
| incs = store.get_includes('') | ||
| incs.each do |inc| | ||
| foundinc = inner_qualify(name, inc, skip) | ||
| possibles.push foundinc unless foundinc.nil? | ||
| end | ||
| end | ||
| return name if store.namespace_exists?(name) | ||
| return possibles.last | ||
| end | ||
| end | ||
| # Get the namespace's type (Class or Module). | ||
@@ -930,46 +878,76 @@ # | ||
| # @param pin [Pin::MethodAlias, Pin::Base] | ||
| # @return [Pin::Method] | ||
| def resolve_method_alias pin | ||
| return pin unless pin.is_a?(Pin::MethodAlias) | ||
| return nil if @method_alias_stack.include?(pin.path) | ||
| @method_alias_stack.push pin.path | ||
| origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope, preserve_generics: true).first | ||
| @method_alias_stack.pop | ||
| return nil if origin.nil? | ||
| include Logging | ||
| private | ||
| # @param alias_pin [Pin::MethodAlias] | ||
| # @return [Pin::Method, nil] | ||
| def resolve_method_alias(alias_pin) | ||
| ancestors = store.get_ancestors(alias_pin.full_context.tag) | ||
| original = nil | ||
| # Search each ancestor for the original method | ||
| ancestors.each do |ancestor_fqns| | ||
| ancestor_fqns = ComplexType.try_parse(ancestor_fqns).force_rooted.namespace | ||
| next if ancestor_fqns.nil? | ||
| ancestor_method_path = "#{ancestor_fqns}#{alias_pin.scope == :instance ? '#' : '.'}#{alias_pin.original}" | ||
| # Search for the original method in the ancestor | ||
| original = store.get_path_pins(ancestor_method_path).find do |candidate_pin| | ||
| next if candidate_pin == alias_pin | ||
| if candidate_pin.is_a?(Pin::MethodAlias) | ||
| # recursively resolve method aliases | ||
| resolved = resolve_method_alias(candidate_pin) | ||
| break resolved if resolved | ||
| end | ||
| candidate_pin.is_a?(Pin::Method) && candidate_pin.scope == alias_pin.scope | ||
| end | ||
| break if original | ||
| end | ||
| # @sg-ignore ignore `received nil` for original | ||
| create_resolved_alias_pin(alias_pin, original) if original | ||
| end | ||
| # Fast path for creating resolved alias pins without individual method stack lookups | ||
| # @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) | ||
| # Build the resolved method pin directly (same logic as resolve_method_alias but without lookup) | ||
| args = { | ||
| location: pin.location, | ||
| type_location: origin.type_location, | ||
| closure: pin.closure, | ||
| name: pin.name, | ||
| comments: origin.comments, | ||
| scope: origin.scope, | ||
| # context: pin.context, | ||
| visibility: origin.visibility, | ||
| signatures: origin.signatures.map(&:clone).freeze, | ||
| attribute: origin.attribute?, | ||
| generics: origin.generics.clone, | ||
| return_type: origin.return_type, | ||
| location: alias_pin.location, | ||
| type_location: original.type_location, | ||
| closure: alias_pin.closure, | ||
| name: alias_pin.name, | ||
| comments: original.comments, | ||
| scope: original.scope, | ||
| visibility: original.visibility, | ||
| signatures: original.signatures.map(&:clone).freeze, | ||
| attribute: original.attribute?, | ||
| generics: original.generics.clone, | ||
| return_type: original.return_type, | ||
| source: :resolve_method_alias | ||
| } | ||
| out = Pin::Method.new **args | ||
| out.signatures.each do |sig| | ||
| resolved_pin = Pin::Method.new **args | ||
| # Clone signatures and parameters | ||
| resolved_pin.signatures.each do |sig| | ||
| sig.parameters = sig.parameters.map(&:clone).freeze | ||
| sig.source = :resolve_method_alias | ||
| sig.parameters.each do |param| | ||
| param.closure = out | ||
| param.closure = resolved_pin | ||
| param.source = :resolve_method_alias | ||
| param.reset_generated! | ||
| end | ||
| sig.closure = out | ||
| sig.closure = resolved_pin | ||
| sig.reset_generated! | ||
| end | ||
| logger.debug { "ApiMap#resolve_method_alias(pin=#{pin}) - returning #{out} from #{origin}" } | ||
| out | ||
| resolved_pin | ||
| end | ||
| include Logging | ||
| private | ||
| # @param namespace_pin [Pin::Namespace] | ||
@@ -994,5 +972,5 @@ # @param rooted_type [ComplexType] | ||
| # @param namespace_pin [Pin::Namespace] | ||
| # @param namespace_pin [Pin::Namespace, Pin::Constant] | ||
| def has_generics?(namespace_pin) | ||
| namespace_pin && !namespace_pin.generics.empty? | ||
| namespace_pin.is_a?(Pin::Namespace) && !namespace_pin.generics.empty? | ||
| end | ||
@@ -999,0 +977,0 @@ |
@@ -7,5 +7,5 @@ # frozen_string_literal: true | ||
| def initialize | ||
| # @type [Hash{Array => Array<Pin::Method>}] | ||
| # @type [Hash{String => Array<Pin::Method>}] | ||
| @methods = {} | ||
| # @type [Hash{(String, Array<String>) => Array<Pin::Base>}] | ||
| # @type [Hash{String, Array<String> => Array<Pin::Base>}] | ||
| @constants = {} | ||
@@ -105,2 +105,3 @@ # @type [Hash{String => String}] | ||
| # @return [Array<Object>] | ||
| def all_caches | ||
@@ -107,0 +108,0 @@ [@methods, @constants, @qualified_namespaces, @receiver_definitions, @clips] |
@@ -18,2 +18,5 @@ # frozen_string_literal: true | ||
| # @return [Set<String>] | ||
| attr_reader :namespaces | ||
| # @return [Hash{String => Array<Pin::Namespace>}] | ||
@@ -40,6 +43,7 @@ def namespace_hash | ||
| s = Set.new | ||
| # @sg-ignore need to support destructured args in blocks | ||
| @pin_select_cache[klass] ||= pin_class_hash.each_with_object(s) { |(key, o), n| n.merge(o) if key <= klass } | ||
| end | ||
| # @return [Hash{String => Array<Pin::Reference::Include>}] | ||
| # @return [Hash{String => Array<String>}] | ||
| def include_references | ||
@@ -49,3 +53,8 @@ @include_references ||= Hash.new { |h, k| h[k] = [] } | ||
| # @return [Hash{String => Array<Pin::Reference::Extend>}] | ||
| # @return [Hash{String => Array<Pin::Reference::Include>}] | ||
| def include_reference_pins | ||
| @include_reference_pins ||= Hash.new { |h, k| h[k] = [] } | ||
| end | ||
| # @return [Hash{String => Array<String>}] | ||
| def extend_references | ||
@@ -55,3 +64,3 @@ @extend_references ||= Hash.new { |h, k| h[k] = [] } | ||
| # @return [Hash{String => Array<Pin::Reference::Prepend>}] | ||
| # @return [Hash{String => Array<String>}] | ||
| def prepend_references | ||
@@ -61,3 +70,3 @@ @prepend_references ||= Hash.new { |h, k| h[k] = [] } | ||
| # @return [Hash{String => Array<Pin::Reference::Superclass>}] | ||
| # @return [Hash{String => Array<String>}] | ||
| def superclass_references | ||
@@ -67,3 +76,4 @@ @superclass_references ||= Hash.new { |h, k| h[k] = [] } | ||
| # @param pins [Array<Pin::Base>] | ||
| # @param pins [Enumerable<Pin::Base>] | ||
| # @return [self] | ||
| def merge pins | ||
@@ -78,2 +88,3 @@ deep_clone.catalog pins | ||
| # @return [self] | ||
| def deep_clone | ||
@@ -93,4 +104,6 @@ Index.allocate.tap do |copy| | ||
| # @param new_pins [Array<Pin::Base>] | ||
| # @param new_pins [Enumerable<Pin::Base>] | ||
| # @return [self] | ||
| def catalog new_pins | ||
| # @type [Hash{Class<generic<T>> => Set<generic<T>>}] | ||
| @pin_select_cache = {} | ||
@@ -119,26 +132,7 @@ pins.concat new_pins | ||
| pins_by_class(klass).each do |pin| | ||
| store_parametric_reference(hash, pin) | ||
| hash[pin.namespace].push pin | ||
| end | ||
| end | ||
| # Add references to a map | ||
| # | ||
| # @param hash [Hash{String => Array<Pin::Reference>}] | ||
| # @param reference_pin [Pin::Reference] | ||
| # | ||
| # @return [void] | ||
| def store_parametric_reference(hash, reference_pin) | ||
| referenced_ns = reference_pin.name | ||
| referenced_tag_params = reference_pin.generic_values | ||
| referenced_tag = referenced_ns + | ||
| if referenced_tag_params && referenced_tag_params.length > 0 | ||
| "<" + referenced_tag_params.join(', ') + ">" | ||
| else | ||
| '' | ||
| end | ||
| referencing_ns = reference_pin.namespace | ||
| hash[referencing_ns].push referenced_tag | ||
| end | ||
| # @return [void] | ||
| def map_overrides | ||
@@ -145,0 +139,0 @@ pins_by_class(Pin::Reference::Override).each do |ovr| |
@@ -48,8 +48,14 @@ # frozen_string_literal: true | ||
| include_object = code_object_at(pin.path, YARD::CodeObjects::ClassObject) | ||
| include_object.instance_mixins.push code_object_map[ref] unless include_object.nil? or include_object.nil? | ||
| unless include_object.nil? || include_object.nil? | ||
| include_object.instance_mixins.push code_object_map[ref.parametrized_tag.to_s] | ||
| end | ||
| end | ||
| store.get_extends(pin.path).each do |ref| | ||
| extend_object = code_object_at(pin.path, YARD::CodeObjects::ClassObject) | ||
| extend_object.instance_mixins.push code_object_map[ref] unless extend_object.nil? or extend_object.nil? | ||
| extend_object.class_mixins.push code_object_map[ref] unless extend_object.nil? or extend_object.nil? | ||
| next unless extend_object | ||
| code_object = code_object_map[ref.parametrized_tag.to_s] | ||
| next unless code_object | ||
| extend_object.class_mixins.push code_object | ||
| # @todo add spec showing why this next line is necessary | ||
| extend_object.instance_mixins.push code_object | ||
| end | ||
@@ -71,3 +77,3 @@ end | ||
| method_object.parameters = pin.parameters.map do |p| | ||
| [p.name, p.asgn_code] | ||
| [p.full_name, p.asgn_code] | ||
| end | ||
@@ -74,0 +80,0 @@ end |
@@ -11,2 +11,3 @@ # frozen_string_literal: true | ||
| def initialize *pinsets | ||
| @pinsets = pinsets | ||
| catalog pinsets | ||
@@ -21,2 +22,7 @@ end | ||
| # @param pinsets [Array<Enumerable<Pin::Base>>] | ||
| # - pinsets[0] = core Ruby pins | ||
| # - pinsets[1] = documentation/gem pins | ||
| # - pinsets[2] = convention pins | ||
| # - pinsets[3] = workspace source pins | ||
| # - pinsets[4] = currently open file pins | ||
| # @return [Boolean] True if the index was updated | ||
@@ -41,2 +47,4 @@ def update *pinsets | ||
| end | ||
| constants.clear | ||
| cached_qualify_superclass.clear | ||
| true | ||
@@ -73,19 +81,29 @@ end | ||
| # @param fq_tag [String] | ||
| 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] | ||
| # @return [Pin::Reference::Superclass] | ||
| def get_superclass fqns | ||
| return nil if fqns.nil? || fqns.empty? | ||
| return BOOLEAN_SUPERCLASS_PIN if %w[TrueClass FalseClass].include?(fqns) | ||
| superclass_references[fqns].first || try_special_superclasses(fqns) | ||
| end | ||
| # @param fq_sub_tag [String] | ||
| # @return [String, nil] | ||
| def get_superclass fq_tag | ||
| raise "Do not prefix fully qualified tags with '::' - #{fq_tag.inspect}" if fq_tag.start_with?('::') | ||
| sub = ComplexType.parse(fq_tag) | ||
| fqns = sub.namespace | ||
| return superclass_references[fq_tag].first if superclass_references.key?(fq_tag) | ||
| return superclass_references[fqns].first if superclass_references.key?(fqns) | ||
| return 'Object' if fqns != 'BasicObject' && namespace_exists?(fqns) | ||
| return 'Object' if fqns == 'Boolean' | ||
| simplified_literal_name = ComplexType.parse("#{fqns}").simplify_literals.name | ||
| return simplified_literal_name if simplified_literal_name != fqns | ||
| nil | ||
| def qualify_superclass fq_sub_tag | ||
| cached_qualify_superclass[fq_sub_tag] || qualify_and_cache_superclass(fq_sub_tag) | ||
| type = ComplexType.try_parse(fq_sub_tag) | ||
| return type.simplify_literals.to_s if type.literal? | ||
| ref = get_superclass(fq_sub_tag) | ||
| return unless ref | ||
| res = constants.dereference(ref) | ||
| return unless res | ||
| res + type.substring | ||
| end | ||
| # @param fqns [String] | ||
| # @return [Array<String>] | ||
| # @return [Array<Pin::Reference::Include>] | ||
| def get_includes fqns | ||
@@ -96,3 +114,3 @@ include_references[fqns] || [] | ||
| # @param fqns [String] | ||
| # @return [Array<String>] | ||
| # @return [Array<Pin::Reference::Prepend>] | ||
| def get_prepends fqns | ||
@@ -103,3 +121,3 @@ prepend_references[fqns] || [] | ||
| # @param fqns [String] | ||
| # @return [Array<String>] | ||
| # @return [Array<Pin::Reference::Extend>] | ||
| def get_extends fqns | ||
@@ -207,4 +225,49 @@ extend_references[fqns] || [] | ||
| # Get all ancestors (superclasses, includes, prepends, extends) for a namespace | ||
| # @param fqns [String] The fully qualified namespace | ||
| # @return [Array<String>] Array of ancestor namespaces including the original | ||
| def get_ancestors(fqns) | ||
| return [] if fqns.nil? || fqns.empty? | ||
| ancestors = [fqns] | ||
| visited = Set.new | ||
| queue = [fqns] | ||
| until queue.empty? | ||
| current = queue.shift | ||
| next if current.nil? || current.empty? || visited.include?(current) | ||
| visited.add(current) | ||
| current = current.gsub(/^::/, '') | ||
| # Add superclass | ||
| ref = get_superclass(current) | ||
| superclass = ref && constants.dereference(ref) | ||
| if superclass && !superclass.empty? && !visited.include?(superclass) | ||
| ancestors << superclass | ||
| queue << superclass | ||
| end | ||
| # Add includes, prepends, and extends | ||
| [get_includes(current), get_prepends(current), get_extends(current)].each do |refs| | ||
| next if refs.nil? | ||
| refs.map(&:parametrized_tag).map(&:to_s).each do |ref| | ||
| next if ref.nil? || ref.empty? || visited.include?(ref) | ||
| ancestors << ref | ||
| queue << ref | ||
| end | ||
| end | ||
| end | ||
| ancestors.compact.uniq | ||
| end | ||
| # @return [Constants] | ||
| def constants | ||
| @constants ||= Constants.new(self) | ||
| end | ||
| private | ||
| # @return [Index] | ||
| def index | ||
@@ -214,4 +277,7 @@ @indexes.last | ||
| # @param pinsets [Array<Enumerable<Pin::Base>>] | ||
| # @return [Boolean] | ||
| def catalog pinsets | ||
| @pinsets = pinsets | ||
| # @type [Array<Index>] | ||
| @indexes = [] | ||
@@ -225,2 +291,4 @@ pinsets.each do |pins| | ||
| end | ||
| constants.clear | ||
| cached_qualify_superclass.clear | ||
| true | ||
@@ -247,3 +315,3 @@ end | ||
| # @return [Hash{String => Array<String>}] | ||
| # @return [Hash{String => Array<Pin::Reference::Include>}] | ||
| def include_references | ||
@@ -253,3 +321,8 @@ index.include_references | ||
| # @return [Hash{String => Array<String>}] | ||
| # @return [Hash{String => Array<Solargraph::Pin::Reference::Include>}] | ||
| def include_reference_pins | ||
| index.include_reference_pins | ||
| end | ||
| # @return [Hash{String => Array<Pin::Reference::Prepend>}] | ||
| def prepend_references | ||
@@ -259,3 +332,3 @@ index.prepend_references | ||
| # @return [Hash{String => Array<String>}] | ||
| # @return [Hash{String => Array<Pin::Reference::Extend>}] | ||
| def extend_references | ||
@@ -276,4 +349,39 @@ index.extend_references | ||
| end | ||
| # @param fqns [String] | ||
| # @return [Pin::Reference::Superclass, nil] | ||
| 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) | ||
| sub = ComplexType.try_parse(fqns) | ||
| return get_superclass(sub.simplify_literals.name) if sub.literal? | ||
| get_superclass(sub.namespace) if sub.namespace != fqns | ||
| end | ||
| # @param fq_sub_tag [String] | ||
| # @return [String, nil] | ||
| def qualify_and_cache_superclass fq_sub_tag | ||
| cached_qualify_superclass[fq_sub_tag] = uncached_qualify_superclass(fq_sub_tag) | ||
| end | ||
| # @return [Hash{String => String, nil}] | ||
| def cached_qualify_superclass | ||
| @cached_qualify_superclass ||= {} | ||
| end | ||
| # @param fq_sub_tag [String] | ||
| # @return [String, nil] | ||
| def uncached_qualify_superclass fq_sub_tag | ||
| type = ComplexType.try_parse(fq_sub_tag) | ||
| return type.simplify_literals.to_s if type.literal? | ||
| ref = get_superclass(fq_sub_tag) | ||
| return unless ref | ||
| res = constants.dereference(ref) | ||
| return unless res | ||
| res + type.substring | ||
| end | ||
| end | ||
| end | ||
| end |
@@ -40,2 +40,3 @@ # frozen_string_literal: true | ||
| # @return [Set<SourceMap>] | ||
| def icebox | ||
@@ -42,0 +43,0 @@ @icebox ||= (source_maps - [live_map]) |
@@ -158,2 +158,3 @@ # frozen_string_literal: true | ||
| # @return [String] | ||
| def tags | ||
@@ -163,2 +164,3 @@ map(&:tag).join(', ') | ||
| # @return [String] | ||
| def simple_tags | ||
@@ -177,2 +179,3 @@ simplify_literals.tags | ||
| # @return [String] | ||
| def desc | ||
@@ -182,2 +185,3 @@ rooted_tags | ||
| # @return [String] | ||
| def rooted_tags | ||
@@ -207,2 +211,3 @@ map(&:rooted_tag).join(', ') | ||
| # @return [self] | ||
| def simplify_literals | ||
@@ -307,3 +312,2 @@ ComplexType.new(map(&:simplify_literals)) | ||
| # # @return [Array<UniqueType>] | ||
| # @sg-ignore | ||
| # @todo To be able to select the right signature above, | ||
@@ -310,0 +314,0 @@ # Chain::Call needs to know the decl type (:arg, :optarg, |
@@ -133,2 +133,3 @@ # frozen_string_literal: true | ||
| # @return [self] | ||
| def namespace_type | ||
@@ -135,0 +136,0 @@ return ComplexType.parse('::Object') if duck_type? |
@@ -52,7 +52,3 @@ # frozen_string_literal: true | ||
| 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) | ||
| # @todo should be able to resolve map; both types have it | ||
| # with same return type | ||
| # @sg-ignore | ||
| key_types.concat(subs[0].map { |u| ComplexType.new([u]) }) | ||
| # @sg-ignore | ||
| subtypes.concat(subs[1].map { |u| ComplexType.new([u]) }) | ||
@@ -109,2 +105,3 @@ elsif parameters_type == :list && name == 'Hash' | ||
| # @return [self] | ||
| def simplify_literals | ||
@@ -121,2 +118,3 @@ transform do |t| | ||
| # @return [String] | ||
| def non_literal_name | ||
@@ -126,2 +124,3 @@ @non_literal_name ||= determine_non_literal_name | ||
| # @return [String] | ||
| def determine_non_literal_name | ||
@@ -178,2 +177,3 @@ # https://github.com/ruby/rbs/blob/master/docs/syntax.md | ||
| # @return [String] | ||
| def desc | ||
@@ -263,3 +263,3 @@ rooted_tags | ||
| # @param context_type [UniqueType, nil] | ||
| # @param resolved_generic_values [Hash{String => ComplexType}] Added to as types are encountered or resolved | ||
| # @param resolved_generic_values [Hash{String => ComplexType, ComplexType::UniqueType}] Added to as types are encountered or resolved | ||
| # @return [UniqueType, ComplexType] | ||
@@ -362,5 +362,5 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {} | ||
| # @param make_rooted [Boolean, nil] | ||
| # @param new_key_types [Array<UniqueType>, nil] | ||
| # @param new_key_types [Array<ComplexType>, nil] | ||
| # @param rooted [Boolean, nil] | ||
| # @param new_subtypes [Array<UniqueType>, nil] | ||
| # @param new_subtypes [Array<ComplexType>, nil] | ||
| # @return [self] | ||
@@ -367,0 +367,0 @@ def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil) |
| # frozen_string_literal: true | ||
| module Solargraph | ||
@@ -15,2 +14,3 @@ # Conventions provide a way to modify an ApiMap based on expectations about | ||
| autoload :DataDefinition, 'solargraph/convention/data_definition' | ||
| autoload :ActiveSupportConcern, 'solargraph/convention/active_support_concern' | ||
@@ -26,2 +26,8 @@ # @type [Set<Convention::Base>] | ||
| # @param convention [Class<Convention::Base>] | ||
| # @return [void] | ||
| def self.unregister convention | ||
| @@conventions.delete_if { |c| c.is_a?(convention) } | ||
| end | ||
| # @param source_map [SourceMap] | ||
@@ -37,3 +43,3 @@ # @return [Environ] | ||
| # @param yard_map [DocMap] | ||
| # @param doc_map [DocMap] | ||
| # @return [Environ] | ||
@@ -48,6 +54,29 @@ def self.for_global(doc_map) | ||
| # Provides any additional method pins based on the described object. | ||
| # | ||
| # @param api_map [ApiMap] | ||
| # @param rooted_tag [String] A fully qualified namespace, with | ||
| # generic parameter values if applicable | ||
| # @param scope [Symbol] :class or :instance | ||
| # @param visibility [Array<Symbol>] :public, :protected, and/or :private | ||
| # @param deep [Boolean] | ||
| # @param skip [Set<String>] | ||
| # @param no_core [Boolean] Skip core classes if true | ||
| # | ||
| # @return [Environ] | ||
| def self.for_object api_map, rooted_tag, scope, visibility, | ||
| deep, skip, no_core | ||
| result = Environ.new | ||
| @@conventions.each do |conv| | ||
| result.merge conv.object(api_map, rooted_tag, scope, visibility, | ||
| deep, skip, no_core) | ||
| end | ||
| result | ||
| end | ||
| register Gemfile | ||
| register Gemspec | ||
| register Rakefile | ||
| register ActiveSupportConcern | ||
| end | ||
| end |
@@ -31,4 +31,21 @@ # frozen_string_literal: true | ||
| end | ||
| # Provides any additional method pins based on e the described object. | ||
| # | ||
| # @param api_map [ApiMap] | ||
| # @param rooted_tag [String] A fully qualified namespace, with | ||
| # generic parameter values if applicable | ||
| # @param scope [Symbol] :class or :instance | ||
| # @param visibility [Array<Symbol>] :public, :protected, and/or :private | ||
| # @param deep [Boolean] | ||
| # @param skip [Set<String>] | ||
| # @param no_core [Boolean] Skip core classes if true | ||
| # | ||
| # @return [Environ] | ||
| def object api_map, rooted_tag, scope, visibility, | ||
| deep, skip, no_core | ||
| EMPTY_ENVIRON | ||
| end | ||
| end | ||
| end | ||
| end |
@@ -38,3 +38,3 @@ # frozen_string_literal: true | ||
| # TODO: Support both arg and kwarg initializers for Data.define | ||
| # @todo Support both arg and kwarg initializers for Data.define | ||
| # Solargraph::SourceMap::Clip#complete_keyword_parameters does not seem to currently take into account [Pin::Method#signatures] hence we only one for :kwarg | ||
@@ -92,2 +92,3 @@ pins.push initialize_method_pin | ||
| # @param attribute_node [Parser::AST::Node] | ||
| # @param attribute_name [String] | ||
| # @return [String, nil] | ||
@@ -94,0 +95,0 @@ def attribute_comments(attribute_node, attribute_name) |
@@ -25,2 +25,3 @@ # frozen_string_literal: true | ||
| # s(:send, nil, :bar)))) | ||
| # @param node [::Parser::AST::Node] | ||
| def match?(node) | ||
@@ -27,0 +28,0 @@ return false unless node&.type == :casgn |
@@ -28,2 +28,4 @@ # frozen_string_literal: true | ||
| # s(:send, nil, :bar))) | ||
| # | ||
| # @param node [Parser::AST::Node] | ||
| def match?(node) | ||
@@ -50,3 +52,3 @@ return false unless node&.type == :class | ||
| # @return [Parser::AST::Node] | ||
| # @param node [Parser::AST::Node] | ||
| def initialize(node) | ||
@@ -53,0 +55,0 @@ @node = node |
@@ -7,3 +7,3 @@ # frozen_string_literal: true | ||
| def local source_map | ||
| return EMPTY_ENVIRON unless File.basename(source_map.filename).end_with?('.gemspec') | ||
| return Convention::Base::EMPTY_ENVIRON unless File.basename(source_map.filename).end_with?('.gemspec') | ||
| @environ ||= Environ.new( | ||
@@ -10,0 +10,0 @@ requires: ['rubygems'], |
@@ -23,3 +23,4 @@ # frozen_string_literal: true | ||
| visibility: :public, | ||
| gates: region.closure.gates.freeze | ||
| gates: region.closure.gates.freeze, | ||
| source: :struct_definition | ||
| ) | ||
@@ -36,3 +37,4 @@ pins.push nspin | ||
| visibility: :private, | ||
| docstring: docstring | ||
| docstring: docstring, | ||
| source: :struct_definition | ||
| ) | ||
@@ -48,3 +50,4 @@ | ||
| location: get_node_location(attribute_node), | ||
| closure: initialize_method_pin | ||
| closure: initialize_method_pin, | ||
| source: :struct_definition | ||
| ) | ||
@@ -59,2 +62,6 @@ ) | ||
| attribute_type = ComplexType.parse(tag_string(docs)) | ||
| return_type_comment = attribute_comment(docs, false) | ||
| param_comment = attribute_comment(docs, true) | ||
| method_pin = Pin::Method.new( | ||
@@ -66,5 +73,8 @@ name: name, | ||
| closure: nspin, | ||
| docstring: YARD::Docstring.new(return_type_comment), | ||
| # even assignments return the value | ||
| comments: attribute_comment(docs, false), | ||
| visibility: :public | ||
| comments: return_type_comment, | ||
| return_type: attribute_type, | ||
| visibility: :public, | ||
| source: :struct_definition | ||
| ) | ||
@@ -76,10 +86,14 @@ | ||
| location: get_node_location(attribute_node), | ||
| closure: nspin, | ||
| comments: attribute_comment(docs, true) | ||
| closure: method_pin, | ||
| return_type: attribute_type, | ||
| comments: param_comment, | ||
| source: :struct_definition | ||
| ) | ||
| pins.push Pin::InstanceVariable.new(name: "@#{attribute_name}", | ||
| closure: method_pin, | ||
| location: get_node_location(attribute_node), | ||
| comments: attribute_comment(docs, false)) | ||
| closure: method_pin, | ||
| location: get_node_location(attribute_node), | ||
| return_type: attribute_type, | ||
| comments: "@type [#{attribute_type.rooted_tags}]", | ||
| source: :struct_definition) | ||
| end | ||
@@ -97,3 +111,3 @@ | ||
| # @return [StructDefintionNode, nil] | ||
| # @return [StructDefintionNode, StructAssignmentNode, nil] | ||
| def struct_definition_node | ||
@@ -123,3 +137,3 @@ @struct_definition_node ||= if StructDefintionNode.match?(node) | ||
| # But since we merge into the struct_comments, then we should interpret either as a param | ||
| comment = '@param ' + attr_name + comment[7..] if comment.start_with?('@return') | ||
| comment = "@param #{attr_name}#{comment[7..]}" if comment.start_with?('@return') | ||
@@ -132,8 +146,17 @@ struct_comments += "\n#{comment}" | ||
| # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_ | ||
| # | ||
| # @return [String] | ||
| def tag_string(tag) | ||
| tag&.types&.join(',') || 'undefined' | ||
| end | ||
| # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute. If nil, this method is a no-op | ||
| # @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? | ||
| suffix = "[#{tag.types&.join(',') || 'undefined'}] #{tag.text}" | ||
| suffix = "[#{tag_string(tag)}] #{tag.text}" | ||
@@ -140,0 +163,0 @@ if for_setter |
@@ -25,2 +25,3 @@ # frozen_string_literal: true | ||
| # s(:send, nil, :bar)))) | ||
| # @param node [Parser::AST::Node] | ||
| def match?(node) | ||
@@ -27,0 +28,0 @@ return false unless node&.type == :casgn |
@@ -28,2 +28,4 @@ # frozen_string_literal: true | ||
| # s(:send, nil, :bar))) | ||
| # | ||
| # @param node [Parser::AST::Node] | ||
| def match?(node) | ||
@@ -50,3 +52,3 @@ return false unless node&.type == :class | ||
| # @return [Parser::AST::Node] | ||
| # @param node [Parser::AST::Node] | ||
| def initialize(node) | ||
@@ -53,0 +55,0 @@ @node = node |
@@ -18,6 +18,6 @@ # frozen_string_literal: true | ||
| begin | ||
| # @type [String] | ||
| gem_path = Gem::Specification.find_by_name('rubocop', version).full_gem_path | ||
| gem_lib_path = File.join(gem_path, 'lib') | ||
| $LOAD_PATH.unshift(gem_lib_path) unless $LOAD_PATH.include?(gem_lib_path) | ||
| # @todo Gem::MissingSpecVersionError is undocumented for some reason | ||
| # @sg-ignore | ||
@@ -24,0 +24,0 @@ rescue Gem::MissingSpecVersionError => e |
@@ -31,3 +31,8 @@ # frozen_string_literal: true | ||
| runner = RuboCop::Runner.new(options, store) | ||
| result = redirect_stdout{ runner.run(paths) } | ||
| # Ensure only one instance of RuboCop::Runner is running at | ||
| # 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) } | ||
| end | ||
@@ -34,0 +39,0 @@ return [] if result.empty? |
@@ -5,2 +5,3 @@ # frozen_string_literal: true | ||
| require 'benchmark' | ||
| require 'open3' | ||
@@ -36,4 +37,6 @@ module Solargraph | ||
| # @return [String, nil] | ||
| attr_reader :rbs_collection_path | ||
| # @return [String, nil] | ||
| attr_reader :rbs_collection_config_path | ||
@@ -57,2 +60,3 @@ | ||
| @environ = Convention.for_global(self) | ||
| @requires.concat @environ.requires if @environ | ||
| load_serialized_gem_pins | ||
@@ -62,2 +66,4 @@ pins.concat @environ.pins | ||
| # @param out [IO] | ||
| # @return [void] | ||
| def cache_all!(out) | ||
@@ -80,4 +86,7 @@ # if we log at debug level: | ||
| # @param gemspec [Gem::Specification] | ||
| # @param out [IO] | ||
| # @return [void] | ||
| def cache_yard_pins(gemspec, out) | ||
| pins = GemPins.build_yard_pins(gemspec) | ||
| pins = GemPins.build_yard_pins(yard_plugins, gemspec) | ||
| PinCache.serialize_yard_gem(gemspec, pins) | ||
@@ -87,2 +96,5 @@ logger.info { "Cached #{pins.length} YARD pins for gem #{gemspec.name}:#{gemspec.version}" } unless pins.empty? | ||
| # @param gemspec [Gem::Specification] | ||
| # @param out [IO] | ||
| # @return [void] | ||
| def cache_rbs_collection_pins(gemspec, out) | ||
@@ -99,2 +111,5 @@ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) | ||
| # @param gemspec [Gem::Specification] | ||
| # @param rebuild [Boolean] whether to rebuild the pins even if they are cached | ||
| # @param out [IO, nil] output stream for logging | ||
| # @return [void] | ||
| def cache(gemspec, rebuild: false, out: nil) | ||
@@ -123,2 +138,3 @@ build_yard = uncached_yard_gemspecs.include?(gemspec) || rebuild | ||
| # @return [Hash{Array(String, String) => Array<Gem::Specification>}] Indexed by gemspec name and version | ||
| def self.all_yard_gems_in_memory | ||
@@ -128,2 +144,3 @@ @yard_gems_in_memory ||= {} | ||
| # @return [Hash{String => Hash{Array(String, String) => Array<Pin::Base>}}] stored by RBS collection path | ||
| def self.all_rbs_collection_gems_in_memory | ||
@@ -133,2 +150,3 @@ @rbs_collection_gems_in_memory ||= {} | ||
| # @return [Hash{Array(String, String) => Array<Pin::Base>}] Indexed by gemspec name and version | ||
| def yard_pins_in_memory | ||
@@ -138,2 +156,3 @@ self.class.all_yard_gems_in_memory | ||
| # @return [Hash{Array(String, String) => Array<Pin::Base>}] Indexed by gemspec name and version | ||
| def rbs_collection_pins_in_memory | ||
@@ -143,2 +162,3 @@ self.class.all_rbs_collection_gems_in_memory[rbs_collection_path] ||= {} | ||
| # @return [Hash{Array(String, String) => Array<Pin::Base>}] Indexed by gemspec name and version | ||
| def self.all_combined_pins_in_memory | ||
@@ -148,2 +168,4 @@ @combined_pins_in_memory ||= {} | ||
| # @todo this should also include an index by the hash of the RBS collection | ||
| # @return [Hash{Array(String, String) => Array<Pin::Base>}] Indexed by gemspec name and version | ||
| def combined_pins_in_memory | ||
@@ -153,2 +175,7 @@ self.class.all_combined_pins_in_memory | ||
| # @return [Array<String>] | ||
| def yard_plugins | ||
| @environ.yard_plugins | ||
| end | ||
| # @return [Set<Gem::Specification>] | ||
@@ -167,3 +194,7 @@ def dependencies | ||
| with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } | ||
| # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash<Array(String, Array<Gem::Specification>), undefined>, received Array<Array(String, Array<Gem::Specification>)> | ||
| # @type [Array<String>] | ||
| paths = Hash[without_gemspecs].keys | ||
| # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash<Array(String, Array<Gem::Specification>), undefined>, received Array<Array(String, Array<Gem::Specification>)> | ||
| # @type [Array<Gem::Specification>] | ||
| gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a | ||
@@ -277,2 +308,4 @@ | ||
| # @param gemspec [Gem::Specification] | ||
| # @param rbs_version_cache_key [String] | ||
| # @return [Array<Pin::Base>, nil] | ||
@@ -293,12 +326,2 @@ def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key | ||
| # @param gemspec [Gem::Specification] | ||
| # @return [Boolean] | ||
| def try_gem_in_memory gemspec | ||
| gempins = DocMap.gems_in_memory[gemspec] | ||
| return false unless gempins | ||
| Solargraph.logger.debug "Found #{gemspec.name} #{gemspec.version} in memory" | ||
| @pins.concat gempins | ||
| true | ||
| end | ||
| # @param path [String] | ||
@@ -310,2 +333,3 @@ # @return [::Array<Gem::Specification>, nil] | ||
| # @type [Gem::Specification, nil] | ||
| gemspec = Gem::Specification.find_by_path(path) | ||
@@ -334,6 +358,8 @@ if gemspec.nil? | ||
| def gemspec_or_preference gemspec | ||
| # :nocov: dormant feature | ||
| return gemspec unless preference_map.key?(gemspec.name) | ||
| return gemspec if gemspec.version == preference_map[gemspec.name].version | ||
| change_gemspec_version gemspec, preference_map[by_path.name].version | ||
| change_gemspec_version gemspec, preference_map[gemspec.name].version | ||
| # :nocov: | ||
| end | ||
@@ -377,2 +403,3 @@ | ||
| # @return [Array<Gem::Specification>] | ||
| def gemspecs_required_from_bundler | ||
@@ -400,2 +427,3 @@ # @todo Handle projects with custom Bundler/Gemfile setups | ||
| # @return [Array<Gem::Specification>] | ||
| def gemspecs_required_from_external_bundle | ||
@@ -402,0 +430,0 @@ logger.info 'Fetching gemspecs required from external bundle' |
@@ -16,12 +16,17 @@ # frozen_string_literal: true | ||
| # @return [Array<Pin::Reference::Override>] | ||
| # @return [Array<Pin::Base>] | ||
| attr_reader :pins | ||
| # @return [Array<String>] | ||
| attr_reader :yard_plugins | ||
| # @param requires [Array<String>] | ||
| # @param domains [Array<String>] | ||
| # @param pins [Array<Pin::Base>] | ||
| def initialize requires: [], domains: [], pins: [] | ||
| # @param yard_plugins[Array<String>] | ||
| def initialize requires: [], domains: [], pins: [], yard_plugins: [] | ||
| @requires = requires | ||
| @domains = domains | ||
| @pins = pins | ||
| @yard_plugins = yard_plugins | ||
| end | ||
@@ -34,2 +39,3 @@ | ||
| pins.clear | ||
| yard_plugins.clear | ||
| self | ||
@@ -44,2 +50,3 @@ end | ||
| pins.concat other.pins | ||
| yard_plugins.concat other.yard_plugins | ||
| self | ||
@@ -46,0 +53,0 @@ end |
@@ -14,13 +14,5 @@ # frozen_string_literal: true | ||
| # @param gemspec [Gem::Specification] | ||
| # @param pins [Array<Pin::Base>] | ||
| # @return [Array<Pin::Base>] | ||
| def self.build_yard_pins(gemspec) | ||
| Yardoc.cache(gemspec) unless Yardoc.cached?(gemspec) | ||
| yardoc = Yardoc.load!(gemspec) | ||
| YardMap::Mapper.new(yardoc, gemspec).map | ||
| end | ||
| # @param pins [Array<Pin::Base>] | ||
| def self.combine_method_pins_by_path(pins) | ||
| # bad_pins = pins.select { |pin| pin.is_a?(Pin::Method) && pin.path == 'StringIO.open' && pin.source == :rbs }; raise "wtf: #{bad_pins}" if bad_pins.length > 1 | ||
| method_pins, alias_pins = pins.partition { |pin| pin.class == Pin::Method } | ||
@@ -34,4 +26,8 @@ by_path = method_pins.group_by(&:path) | ||
| # @param pins [Array<Pin::Method>] | ||
| # @return [Pin::Method, nil] | ||
| def self.combine_method_pins(*pins) | ||
| out = pins.reduce(nil) do |memo, pin| | ||
| # @type [Pin::Method, nil] | ||
| combined_pin = nil | ||
| out = pins.reduce(combined_pin) do |memo, pin| | ||
| next pin if memo.nil? | ||
@@ -50,4 +46,14 @@ if memo == pin && memo.source != :combined | ||
| # @param yard_plugins [Array<String>] The names of YARD plugins to use. | ||
| # @param gemspec [Gem::Specification] | ||
| # @return [Array<Pin::Base>] | ||
| def self.build_yard_pins(yard_plugins, gemspec) | ||
| Yardoc.cache(yard_plugins, gemspec) unless Yardoc.cached?(gemspec) | ||
| yardoc = Yardoc.load!(gemspec) | ||
| YardMap::Mapper.new(yardoc, gemspec).map | ||
| end | ||
| # @param yard_pins [Array<Pin::Base>] | ||
| # @param rbs_map [RbsMap] | ||
| # @param rbs_pins [Array<Pin::Base>] | ||
| # | ||
| # @return [Array<Pin::Base>] | ||
@@ -54,0 +60,0 @@ def self.combine(yard_pins, rbs_pins) |
@@ -302,2 +302,3 @@ # frozen_string_literal: true | ||
| # @return [String] | ||
| def command_path | ||
@@ -720,3 +721,3 @@ options['commandPath'] || 'solargraph' | ||
| # | ||
| # @return [Hash{Integer => Solargraph::LanguageServer::Host}] | ||
| # @return [Hash{Integer => Solargraph::LanguageServer::Request}] | ||
| def requests | ||
@@ -723,0 +724,0 @@ @requests ||= {} |
@@ -98,2 +98,3 @@ # frozen_string_literal: true | ||
| # @return [Hash{String => undefined}] | ||
| def options | ||
@@ -122,2 +123,3 @@ @options ||= {}.freeze | ||
| # @param library [Solargraph::Library] | ||
| # @param progress [Solargraph::LanguageServer::Progress, nil] | ||
| # @return [void] | ||
@@ -124,0 +126,0 @@ def update progress |
@@ -75,2 +75,3 @@ # frozen_string_literal: true | ||
| # @return [Hash, nil] | ||
| def next_message | ||
@@ -80,2 +81,3 @@ cancel_message || next_priority | ||
| # @return [Hash, nil] | ||
| def cancel_message | ||
@@ -91,2 +93,3 @@ # Handle cancellations first | ||
| # @return [Hash, nil] | ||
| def next_priority | ||
@@ -93,0 +96,0 @@ # Prioritize updates and version-dependent messages for performance |
@@ -19,3 +19,3 @@ # frozen_string_literal: true | ||
| # @return [Hash{String => Array<undefined>, Hash{String => undefined}, String, Integer}] | ||
| # @return [Hash{String => undefined}] | ||
| attr_reader :params | ||
@@ -83,2 +83,3 @@ | ||
| # @return [void] | ||
| def accept_or_cancel | ||
@@ -85,0 +86,0 @@ if host.cancel?(id) |
@@ -86,3 +86,3 @@ # frozen_string_literal: true | ||
| @available ||= begin | ||
| # @sg-ignore | ||
| # @sg-ignore Variable type could not be inferred for tuple | ||
| # @type [Gem::Dependency, nil] | ||
@@ -89,0 +89,0 @@ tuple = CheckGemVersion.fetcher.search_for_dependency(Gem::Dependency.new('solargraph')).flatten.first |
@@ -13,2 +13,3 @@ # frozen_string_literal: true | ||
| # @return [Array<Hash>] | ||
| def code_location | ||
@@ -25,2 +26,3 @@ suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) | ||
| # @return [Array<Hash>] | ||
| def require_location | ||
@@ -27,0 +29,0 @@ # @todo Terrible hack |
@@ -22,4 +22,10 @@ # frozen_string_literal: true | ||
| options[:stdin] = original | ||
| corrections = redirect_stdout do | ||
| ::RuboCop::Runner.new(options, ::RuboCop::ConfigStore.new).run(paths) | ||
| # Ensure only one instance of RuboCop::Runner is running at | ||
| # a time - it uses 'chdir' to read config files with ERB, | ||
| # which can conflict with other chdirs. | ||
| corrections = Solargraph::CHDIR_MUTEX.synchronize do | ||
| redirect_stdout do | ||
| ::RuboCop::Runner.new(options, ::RuboCop::ConfigStore.new).run(paths) | ||
| end | ||
| end | ||
@@ -38,2 +44,3 @@ result = options[:stdin] | ||
| # @param corrections [String] | ||
| # @return [void] | ||
| def log_corrections(corrections) | ||
@@ -50,2 +57,4 @@ corrections = corrections&.strip | ||
| # @param file_uri [String] | ||
| # @return [Hash{String => undefined}] | ||
| def config_for(file_uri) | ||
@@ -58,3 +67,5 @@ conf = host.formatter_config(file_uri) | ||
| # @param file_uri [String] | ||
| # @param config [Hash{String => String}] | ||
| # @return [Array<String>] | ||
| def cli_args file_uri, config | ||
@@ -78,2 +89,4 @@ file = UriHelpers.uri_to_file(file_uri) | ||
| # @param config [Hash{String => String}] | ||
| # @sg-ignore | ||
| # @return [Class<RuboCop::Formatter::BaseFormatter>] | ||
| def formatter_class(config) | ||
@@ -91,2 +104,3 @@ if self.class.const_defined?('BlankRubocopFormatter') | ||
| # @param value [Array, String] | ||
| # @return [String] | ||
| def cop_list(value) | ||
@@ -93,0 +107,0 @@ value = value.join(',') if value.respond_to?(:join) |
@@ -13,2 +13,3 @@ # frozen_string_literal: true | ||
| # @return [Array<Hash>] | ||
| def code_location | ||
@@ -15,0 +16,0 @@ suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column) |
@@ -12,2 +12,3 @@ # frozen_string_literal: true | ||
| # @return [void] | ||
| def add_folders | ||
@@ -18,2 +19,3 @@ return unless params['event'] && params['event']['added'] | ||
| # @return [void] | ||
| def remove_folders | ||
@@ -20,0 +22,0 @@ return unless params['event'] && params['event']['removed'] |
@@ -42,2 +42,3 @@ # frozen_string_literal: true | ||
| # @param percentage [Integer] | ||
| # @return [void] | ||
| def begin message, percentage | ||
@@ -51,2 +52,3 @@ @kind = 'begin' | ||
| # @param percentage [Integer] | ||
| # @return [void] | ||
| def report message, percentage | ||
@@ -59,2 +61,3 @@ @kind = 'report' | ||
| # @param message [String] | ||
| # @return [void] | ||
| def finish message | ||
@@ -68,2 +71,3 @@ @kind = 'end' | ||
| # @param host [Solargraph::LanguageServer::Host] | ||
| # @return [void] | ||
| def send host | ||
@@ -98,2 +102,3 @@ return unless host.client_supports_progress? && !finished? | ||
| # @return [Hash] | ||
| def build | ||
@@ -109,2 +114,3 @@ { | ||
| # @return [Hash] | ||
| def build_value | ||
@@ -124,2 +130,3 @@ case kind | ||
| # @param host [Host] | ||
| # @return [void] | ||
| def keep_alive host | ||
@@ -137,2 +144,3 @@ mutex.synchronize { @last = Time.now } | ||
| # @return [Mutex] | ||
| def mutex | ||
@@ -139,0 +147,0 @@ @mutex ||= Mutex.new |
@@ -19,2 +19,3 @@ # frozen_string_literal: true | ||
| # @return [void] | ||
| def send_response | ||
@@ -21,0 +22,0 @@ # noop |
@@ -405,4 +405,4 @@ # frozen_string_literal: true | ||
| if line == 'all!' | ||
| Diagnostics.reporters.each do |reporter| | ||
| repargs[reporter] ||= [] | ||
| Diagnostics.reporters.each do |reporter_name| | ||
| repargs[Diagnostics.reporter(reporter_name)] ||= [] | ||
| end | ||
@@ -441,13 +441,2 @@ else | ||
| # Get an array of foldable ranges for the specified file. | ||
| # | ||
| # @deprecated The library should not need to handle folding ranges. The | ||
| # source itself has all the information it needs. | ||
| # | ||
| # @param filename [String] | ||
| # @return [Array<Range>] | ||
| def folding_ranges filename | ||
| read(filename).folding_ranges | ||
| end | ||
| # Create a library from a directory. | ||
@@ -516,3 +505,3 @@ # | ||
| # @return [Hash{String => Set<String>}] | ||
| # @return [Hash{String => Array<String>}] | ||
| def source_map_external_require_hash | ||
@@ -525,2 +514,3 @@ @source_map_external_require_hash ||= {} | ||
| def find_external_requires source_map | ||
| # @type [Set<String>] | ||
| new_set = source_map.requires.map(&:name).to_set | ||
@@ -628,2 +618,3 @@ # return if new_set == source_map_external_require_hash[source_map.filename] | ||
| # @return [Array<Gem::Specification>] | ||
| def cacheable_specs | ||
@@ -639,2 +630,3 @@ cacheable = api_map.uncached_yard_gemspecs + | ||
| # @return [Array<Gem::Specification>] | ||
| def queued_gemspec_cache | ||
@@ -681,2 +673,3 @@ @queued_gemspec_cache ||= [] | ||
| # @return [void] | ||
| def sync_catalog | ||
@@ -687,4 +680,4 @@ return if @sync_count == 0 | ||
| logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}" | ||
| source_map_hash.values.each { |map| find_external_requires(map) } | ||
| api_map.catalog bench | ||
| source_map_hash.values.each { |map| find_external_requires(map) } | ||
| logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)" | ||
@@ -691,0 +684,0 @@ logger.info "#{api_map.uncached_yard_gemspecs.length} uncached YARD gemspecs" |
@@ -28,2 +28,3 @@ # frozen_string_literal: true | ||
| # @param other [self] | ||
| def <=>(other) | ||
@@ -64,2 +65,3 @@ return nil unless other.is_a?(Location) | ||
| # @param node [Parser::AST::Node, nil] | ||
| # @return [Location, nil] | ||
| def self.from_node(node) | ||
@@ -66,0 +68,0 @@ return nil if node.nil? || node.loc.nil? |
@@ -14,4 +14,13 @@ # frozen_string_literal: true | ||
| } | ||
| @@logger = Logger.new(STDERR, level: DEFAULT_LOG_LEVEL) | ||
| configured_level = ENV.fetch('SOLARGRAPH_LOG', nil) | ||
| level = if LOG_LEVELS.keys.include?(configured_level) | ||
| LOG_LEVELS.fetch(configured_level) | ||
| else | ||
| if configured_level | ||
| warn "Invalid value for SOLARGRAPH_LOG: #{configured_level.inspect} - " \ | ||
| "valid values are #{LOG_LEVELS.keys}" | ||
| end | ||
| DEFAULT_LOG_LEVEL | ||
| end | ||
| @@logger = Logger.new(STDERR, level: level) | ||
| # @sg-ignore Fix cvar issue | ||
@@ -18,0 +27,0 @@ @@logger.formatter = proc do |severity, datetime, progname, msg| |
@@ -30,4 +30,6 @@ # frozen_string_literal: true | ||
| # @param text [String] | ||
| # @sg-ignore https://github.com/lsegal/yard/pull/1615 | ||
| # @return [String] | ||
| def htmlify text | ||
| # @type [String] | ||
| YARD::Templates::Helpers::Markup::RDocMarkup.new(text).to_html | ||
@@ -74,4 +76,6 @@ end | ||
| # @param locals [Hash] | ||
| # @sg-ignore | ||
| # @return [String] | ||
| def render template, layout: true, locals: {} | ||
| # @type [String] | ||
| @render_method.call(template, layout: layout, locals: locals) | ||
@@ -78,0 +82,0 @@ end |
@@ -6,2 +6,9 @@ require 'ripper' | ||
| class CommentRipper < Ripper::SexpBuilderPP | ||
| # @!override Ripper::SexpBuilder#on_embdoc_beg | ||
| # @return [Array(Symbol, String, Array)] | ||
| # @!override Ripper::SexpBuilder#on_embdoc | ||
| # @return [Array(Symbol, String, Array)] | ||
| # @!override Ripper::SexpBuilder#on_embdoc_end | ||
| # @return [Array(Symbol, String, Array)] | ||
| # @param src [String] | ||
@@ -55,3 +62,3 @@ # @param filename [String] | ||
| # @return [Hash{Integer => String}] | ||
| # @return [Hash{Integer => Solargraph::Parser::Snippet}] | ||
| def parse | ||
@@ -58,0 +65,0 @@ @comments = {} |
@@ -7,2 +7,3 @@ module Solargraph | ||
| # @param locals [Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>] | ||
| # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil] | ||
| def initialize(locals, enclosing_breakable_pin = nil) | ||
@@ -14,4 +15,9 @@ @locals = locals | ||
| # @param and_node [Parser::AST::Node] | ||
| # @param true_ranges [Array<Range>] | ||
| # | ||
| # @return [void] | ||
| def process_and(and_node, true_ranges = []) | ||
| # @type [Parser::AST::Node] | ||
| lhs = and_node.children[0] | ||
| # @type [Parser::AST::Node] | ||
| rhs = and_node.children[1] | ||
@@ -28,2 +34,4 @@ | ||
| # @param if_node [Parser::AST::Node] | ||
| # | ||
| # @return [void] | ||
| def process_if(if_node) | ||
@@ -42,3 +50,5 @@ # | ||
| conditional_node = if_node.children[0] | ||
| # @type [Parser::AST::Node] | ||
| then_clause = if_node.children[1] | ||
| # @type [Parser::AST::Node] | ||
| else_clause = if_node.children[2] | ||
@@ -79,4 +89,7 @@ | ||
| # @param pins [Array<Pin::LocalVariable>] | ||
| # @param name [String] | ||
| # @param closure [Pin::Closure] | ||
| # @param location [Location] | ||
| # | ||
| # @return [Array<Pin::LocalVariable>] | ||
| def self.visible_pins(pins, name, closure, location) | ||
@@ -115,3 +128,6 @@ logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location})" } | ||
| # @param pin [Pin::LocalVariable] | ||
| # @param if_node [Parser::AST::Node] | ||
| # @param downcast_type_name [String] | ||
| # @param presence [Range] | ||
| # | ||
| # @return [void] | ||
| def add_downcast_local(pin, downcast_type_name, presence) | ||
@@ -135,2 +151,3 @@ # @todo Create pin#update method | ||
| # @param presences [Array<Range>] | ||
| # | ||
| # @return [void] | ||
@@ -152,2 +169,5 @@ def process_facts(facts_by_pin, presences) | ||
| # @param conditional_node [Parser::AST::Node] | ||
| # @param true_ranges [Array<Range>] | ||
| # | ||
| # @return [void] | ||
| def process_conditional(conditional_node, true_ranges) | ||
@@ -187,2 +207,6 @@ if conditional_node.type == :send | ||
| # @param variable_name [String] | ||
| # @param position [Position] | ||
| # | ||
| # @return [Solargraph::Pin::LocalVariable, nil] | ||
| def find_local(variable_name, position) | ||
@@ -194,2 +218,6 @@ pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) } | ||
| # @param isa_node [Parser::AST::Node] | ||
| # @param true_presences [Array<Range>] | ||
| # | ||
| # @return [void] | ||
| def process_isa(isa_node, true_presences) | ||
@@ -210,6 +238,8 @@ isa_type_name, variable_name = parse_isa(isa_node) | ||
| # @param node [Parser::AST::Node] | ||
| # | ||
| # @return [String, nil] | ||
| def type_name(node) | ||
| # e.g., | ||
| # s(:const, nil, :Baz) | ||
| return unless node.type == :const | ||
| return unless node&.type == :const | ||
| module_node = node.children[0] | ||
@@ -226,4 +256,2 @@ class_node = node.children[1] | ||
| # @todo "return type could not be inferred" should not trigger here | ||
| # @sg-ignore | ||
| # @param clause_node [Parser::AST::Node] | ||
@@ -230,0 +258,0 @@ def always_breaks?(clause_node) |
| module Solargraph | ||
| module Parser | ||
| class NodeMethods | ||
| module NodeMethods | ||
| module_function | ||
@@ -77,3 +77,3 @@ | ||
| # @param node [Parser::AST::Node] | ||
| # @return [Hash{Parser::AST::Node => Source::Chain}] | ||
| # @return [Hash{Symbol => Source::Chain}] | ||
| def convert_hash node | ||
@@ -80,0 +80,0 @@ raise NotImplementedError |
@@ -12,3 +12,3 @@ # frozen_string_literal: true | ||
| class << self | ||
| # @type [Hash<Symbol, Array<Class<NodeProcessor::Base>>>] | ||
| # @type [Hash{Symbol => Array<Class<NodeProcessor::Base>>}] | ||
| @@processors ||= {} | ||
@@ -21,3 +21,3 @@ | ||
| # @param cls [Class<NodeProcessor::Base>] | ||
| # @return [Class<NodeProcessor::Base>] | ||
| # @return [Array<Class<NodeProcessor::Base>>] | ||
| def register type, cls | ||
@@ -28,2 +28,6 @@ @@processors[type] ||= [] | ||
| # @param type [Symbol] | ||
| # @param cls [Class<NodeProcessor::Base>] | ||
| # | ||
| # @return [void] | ||
| def deregister type, cls | ||
@@ -30,0 +34,0 @@ @@processors[type].delete(cls) |
@@ -16,3 +16,3 @@ # frozen_string_literal: true | ||
| # @return [Array<Pin::BaseVariable>] | ||
| # @return [Array<Pin::LocalVariable>] | ||
| attr_reader :locals | ||
@@ -19,0 +19,0 @@ |
@@ -20,3 +20,3 @@ # frozen_string_literal: true | ||
| # @param filename [String, nil] | ||
| # @return [Array(Parser::AST::Node, Hash{Integer => String})] | ||
| # @return [Array(Parser::AST::Node, Hash{Integer => Solargraph::Parser::Snippet})] | ||
| def parse_with_comments code, filename = nil | ||
@@ -23,0 +23,0 @@ node = parse(code, filename) |
@@ -12,2 +12,3 @@ # frozen_string_literal: true | ||
| # @return [String] | ||
| # @sg-ignore | ||
| def string_value(token) | ||
@@ -14,0 +15,0 @@ value(token) |
@@ -10,2 +10,3 @@ # frozen_string_literal: true | ||
| include NodeMethods | ||
| Chain = Source::Chain | ||
@@ -102,3 +103,4 @@ | ||
| elsif n.type == :or_asgn | ||
| result.concat generate_links n.children[1] | ||
| new_node = n.updated(n.children[0].type, n.children[0].children + [n.children[1]]) | ||
| result.concat generate_links new_node | ||
| elsif [:class, :module, :def, :defs].include?(n.type) | ||
@@ -105,0 +107,0 @@ # @todo Undefined or what? |
@@ -123,3 +123,3 @@ # frozen_string_literal: true | ||
| # @param node [Parser::AST::Node] | ||
| # @return [Hash{Parser::AST::Node => Chain}] | ||
| # @return [Hash{Symbol => Chain}] | ||
| def convert_hash node | ||
@@ -183,2 +183,3 @@ return {} unless Parser.is_ast_node?(node) | ||
| result.push node | ||
| result.concat call_nodes_from(node.children.first) | ||
| node.children[2..-1].each { |child| result.concat call_nodes_from(child) } | ||
@@ -237,2 +238,3 @@ elsif [:super, :zsuper].include?(node.type) | ||
| end | ||
| # @type [AST::Node, nil] | ||
| prev = nil | ||
@@ -248,3 +250,3 @@ tree.each do |node| | ||
| else | ||
| return node if source.code[0..offset-1] =~ /\([^\(]*\z/ | ||
| return node if source.code[0..offset-1] =~ /\([^(]*\z/ | ||
| end | ||
@@ -251,0 +253,0 @@ end |
@@ -22,3 +22,3 @@ # frozen_string_literal: true | ||
| end | ||
| pins.push Solargraph::Pin::Block.new( | ||
| block_pin = Solargraph::Pin::Block.new( | ||
| location: location, | ||
@@ -32,3 +32,4 @@ closure: parent, | ||
| ) | ||
| process_children region.update(closure: pins.last) | ||
| pins.push block_pin | ||
| process_children region.update(closure: block_pin) | ||
| end | ||
@@ -35,0 +36,0 @@ |
@@ -14,2 +14,4 @@ # frozen_string_literal: true | ||
| position = get_node_start_position(node) | ||
| # @sg-ignore | ||
| # @type [Solargraph::Pin::Breakable, nil] | ||
| enclosing_breakable_pin = pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location.range.contain?(position)}.last | ||
@@ -16,0 +18,0 @@ FlowSensitiveTyping.new(locals, enclosing_breakable_pin).process_if(node) |
@@ -25,4 +25,7 @@ # frozen_string_literal: true | ||
| masgn = node | ||
| # @type [Parser::AST::Node] | ||
| mlhs = masgn.children.fetch(0) | ||
| # @type [Array<Parser::AST::Node>] | ||
| lhs_arr = mlhs.children | ||
| # @type [Parser::AST::Node] | ||
| mass_rhs = node.children.fetch(1) | ||
@@ -29,0 +32,0 @@ |
@@ -11,28 +11,40 @@ # frozen_string_literal: true | ||
| def process | ||
| # @sg-ignore Variable type could not be inferred for method_name | ||
| # @type [Symbol] | ||
| 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}") | ||
| return process_children | ||
| end | ||
| # :nocov: | ||
| if node.children[0].nil? | ||
| if [:private, :public, :protected].include?(node.children[1]) | ||
| if [:private, :public, :protected].include?(method_name) | ||
| process_visibility | ||
| elsif node.children[1] == :module_function | ||
| elsif method_name == :module_function | ||
| process_module_function | ||
| elsif [:attr_reader, :attr_writer, :attr_accessor].include?(node.children[1]) | ||
| elsif [:attr_reader, :attr_writer, :attr_accessor].include?(method_name) | ||
| process_attribute | ||
| elsif node.children[1] == :include | ||
| elsif method_name == :include | ||
| process_include | ||
| elsif node.children[1] == :extend | ||
| elsif method_name == :extend | ||
| process_extend | ||
| elsif node.children[1] == :prepend | ||
| elsif method_name == :prepend | ||
| process_prepend | ||
| elsif node.children[1] == :require | ||
| elsif method_name == :require | ||
| process_require | ||
| elsif node.children[1] == :autoload | ||
| elsif method_name == :autoload | ||
| process_autoload | ||
| elsif node.children[1] == :private_constant | ||
| elsif method_name == :private_constant | ||
| process_private_constant | ||
| elsif node.children[1] == :alias_method && node.children[2] && node.children[2] && node.children[2].type == :sym && node.children[3] && node.children[3].type == :sym | ||
| # @sg-ignore | ||
| elsif method_name == :alias_method && node.children[2] && node.children[2] && node.children[2].type == :sym && node.children[3] && node.children[3].type == :sym | ||
| process_alias_method | ||
| elsif node.children[1] == :private_class_method && node.children[2].is_a?(AST::Node) | ||
| # @sg-ignore | ||
| elsif method_name == :private_class_method && node.children[2].is_a?(AST::Node) | ||
| # Processing a private class can potentially handle children on its own | ||
| return if process_private_class_method | ||
| end | ||
| elsif node.children[1] == :require && node.children[0].to_s == '(const nil :Bundler)' | ||
| # @sg-ignore | ||
| 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) | ||
@@ -49,2 +61,11 @@ end | ||
| node.children[2..-1].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}") | ||
| return process_children | ||
| end | ||
| # :nocov: | ||
| if child.is_a?(AST::Node) && (child.type == :sym || child.type == :str) | ||
@@ -55,6 +76,6 @@ name = child.children[0].to_s | ||
| # @todo Smelly instance variable access | ||
| pin.instance_variable_set(:@visibility, node.children[1]) | ||
| pin.instance_variable_set(:@visibility, visibility) | ||
| end | ||
| else | ||
| process_children region.update(visibility: node.children[1]) | ||
| process_children region.update(visibility: visibility) | ||
| end | ||
@@ -61,0 +82,0 @@ end |
@@ -26,4 +26,6 @@ # frozen_string_literal: true | ||
| # @param namespace [String] | ||
| # @param closure [Pin::Closure, nil] | ||
| # @param scope [Symbol, nil] | ||
| # @param visibility [Symbol] | ||
| # @param lvars [Array<Symbol>] | ||
| def initialize source: Solargraph::Source.load_string(''), closure: nil, | ||
@@ -49,2 +51,3 @@ scope: nil, visibility: :public, lvars: [] | ||
| # @param visibility [Symbol, nil] | ||
| # @param lvars [Array<Symbol>, nil] | ||
| # @return [Region] | ||
@@ -51,0 +54,0 @@ def update closure: nil, scope: nil, visibility: nil, lvars: nil |
@@ -9,2 +9,4 @@ module Solargraph | ||
| # @param range [Solargraph::Range] | ||
| # @param text [String] | ||
| def initialize range, text | ||
@@ -11,0 +13,0 @@ @range = range |
@@ -0,1 +1,2 @@ | ||
| require 'yard-activesupport-concern' | ||
| require 'fileutils' | ||
@@ -29,6 +30,12 @@ require 'rbs' | ||
| # @param gemspec [Gem::Specification] | ||
| # @return [String] | ||
| def yardoc_path gemspec | ||
| File.join(base_dir, "yard-#{YARD::VERSION}", "#{gemspec.name}-#{gemspec.version}.yardoc") | ||
| File.join(base_dir, | ||
| "yard-#{YARD::VERSION}", | ||
| "yard-activesupport-concern-#{YARD::ActiveSupport::Concern::VERSION}", | ||
| "#{gemspec.name}-#{gemspec.version}.yardoc") | ||
| end | ||
| # @return [String] | ||
| def stdlib_path | ||
@@ -38,2 +45,4 @@ File.join(work_dir, 'stdlib') | ||
| # @param require [String] | ||
| # @return [String] | ||
| def stdlib_require_path require | ||
@@ -43,2 +52,4 @@ File.join(stdlib_path, "#{require}.ser") | ||
| # @param require [String] | ||
| # @return [Array<Pin::Base>] | ||
| def deserialize_stdlib_require require | ||
@@ -48,2 +59,5 @@ load(stdlib_require_path(require)) | ||
| # @param require [String] | ||
| # @param pins [Array<Pin::Base>] | ||
| # @return [void] | ||
| def serialize_stdlib_require require, pins | ||
@@ -53,2 +67,3 @@ save(stdlib_require_path(require), pins) | ||
| # @return [String] | ||
| def core_path | ||
@@ -58,2 +73,3 @@ File.join(work_dir, 'core.ser') | ||
| # @return [Array<Pin::Base>] | ||
| def deserialize_core | ||
@@ -63,2 +79,4 @@ load(core_path) | ||
| # @param pins [Array<Pin::Base>] | ||
| # @return [void] | ||
| def serialize_core pins | ||
@@ -68,2 +86,4 @@ save(core_path, pins) | ||
| # @param gemspec [Gem::Specification] | ||
| # @return [String] | ||
| def yard_gem_path gemspec | ||
@@ -73,2 +93,4 @@ File.join(work_dir, 'yard', "#{gemspec.name}-#{gemspec.version}.ser") | ||
| # @param gemspec [Gem::Specification] | ||
| # @return [Array<Pin::Base>] | ||
| def deserialize_yard_gem(gemspec) | ||
@@ -78,2 +100,5 @@ load(yard_gem_path(gemspec)) | ||
| # @param gemspec [Gem::Specification] | ||
| # @param pins [Array<Pin::Base>] | ||
| # @return [void] | ||
| def serialize_yard_gem(gemspec, pins) | ||
@@ -83,2 +108,4 @@ save(yard_gem_path(gemspec), pins) | ||
| # @param gemspec [Gem::Specification] | ||
| # @return [Boolean] | ||
| def has_yard?(gemspec) | ||
@@ -88,2 +115,5 @@ exist?(yard_gem_path(gemspec)) | ||
| # @param gemspec [Gem::Specification] | ||
| # @param hash [String, nil] | ||
| # @return [String] | ||
| def rbs_collection_path(gemspec, hash) | ||
@@ -93,2 +123,4 @@ File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser") | ||
| # @param gemspec [Gem::Specification] | ||
| # @return [String] | ||
| def rbs_collection_path_prefix(gemspec) | ||
@@ -98,2 +130,5 @@ File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-") | ||
| # @param gemspec [Gem::Specification] | ||
| # @param hash [String, nil] | ||
| # @return [Array<Pin::Base>] | ||
| def deserialize_rbs_collection_gem(gemspec, hash) | ||
@@ -103,2 +138,6 @@ load(rbs_collection_path(gemspec, hash)) | ||
| # @param gemspec [Gem::Specification] | ||
| # @param hash [String, nil] | ||
| # @param pins [Array<Pin::Base>]n | ||
| # @return [void] | ||
| def serialize_rbs_collection_gem(gemspec, hash, pins) | ||
@@ -108,2 +147,5 @@ save(rbs_collection_path(gemspec, hash), pins) | ||
| # @param gemspec [Gem::Specification] | ||
| # @param hash [String, nil] | ||
| # @return [String] | ||
| def combined_path(gemspec, hash) | ||
@@ -113,2 +155,4 @@ File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser") | ||
| # @param gemspec [Gem::Specification] | ||
| # @return [String] | ||
| def combined_path_prefix(gemspec) | ||
@@ -118,2 +162,6 @@ File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-") | ||
| # @param gemspec [Gem::Specification] | ||
| # @param hash [String, nil] | ||
| # @param pins [Array<Pin::Base>] | ||
| # @return [void] | ||
| def serialize_combined_gem(gemspec, hash, pins) | ||
@@ -123,2 +171,5 @@ save(combined_path(gemspec, hash), pins) | ||
| # @param gemspec [Gem::Specification] | ||
| # @param hash [String, nil] | ||
| # @return [Array<Pin::Base>] | ||
| def deserialize_combined_gem gemspec, hash | ||
@@ -128,2 +179,5 @@ load(combined_path(gemspec, hash)) | ||
| # @param gemspec [Gem::Specification] | ||
| # @param hash [String, nil] | ||
| # @return [Boolean] | ||
| def has_rbs_collection?(gemspec, hash) | ||
@@ -133,2 +187,3 @@ exist?(rbs_collection_path(gemspec, hash)) | ||
| # @return [void] | ||
| def uncache_core | ||
@@ -138,2 +193,3 @@ uncache(core_path) | ||
| # @return [void] | ||
| def uncache_stdlib | ||
@@ -143,2 +199,5 @@ uncache(stdlib_path) | ||
| # @param gemspec [Gem::Specification] | ||
| # @param out [IO, nil] | ||
| # @return [void] | ||
| def uncache_gem(gemspec, out: nil) | ||
@@ -169,7 +228,8 @@ uncache(yardoc_path(gemspec), out: out) | ||
| # @param path [String] | ||
| def exist? *path | ||
| File.file? join(*path) | ||
| File.file? File.join(*path) | ||
| end | ||
| # @param path [Array<String>] | ||
| # @param file [String] | ||
| # @param pins [Array<Pin::Base>] | ||
@@ -185,4 +245,4 @@ # @return [void] | ||
| # @param path_segments [Array<String>] | ||
| # @return [void] | ||
| # @param path_segments [Array<String>] | ||
| def uncache *path_segments, out: nil | ||
@@ -189,0 +249,0 @@ path = File.join(*path_segments) |
@@ -6,4 +6,4 @@ # frozen_string_literal: true | ||
| class BaseVariable < Base | ||
| # include Solargraph::Source::NodeMethods | ||
| include Solargraph::Parser::NodeMethods | ||
| # include Solargraph::Source::NodeMethods | ||
@@ -47,3 +47,2 @@ # @return [Parser::AST::Node, nil] | ||
| # @sg-ignore | ||
| def nil_assignment? | ||
@@ -50,0 +49,0 @@ # this will always be false - should it be return_type == |
@@ -16,6 +16,6 @@ # frozen_string_literal: true | ||
| # @return [Solargraph::Location] | ||
| # @return [Solargraph::Location, nil] | ||
| attr_reader :location | ||
| # @return [Solargraph::Location] | ||
| # @return [Solargraph::Location, nil] | ||
| attr_reader :type_location | ||
@@ -32,2 +32,7 @@ | ||
| # @type [::Numeric, nil] A priority for determining if pins should be combined or not | ||
| # A nil priority is considered the be the lowest. All code, yard & rbs pins have nil priority | ||
| # Between 2 pins, the one with the higher priority gets chosen. If the priorities are equal, they are combined. | ||
| attr_reader :combine_priority | ||
| def presence_certain? | ||
@@ -45,3 +50,4 @@ true | ||
| # @param directives [::Array<YARD::Tags::Directive>, nil] | ||
| def initialize location: nil, type_location: nil, closure: nil, source: nil, name: '', comments: '', docstring: nil, directives: 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 | ||
| @location = location | ||
@@ -56,2 +62,4 @@ @type_location = type_location | ||
| @directives = directives | ||
| @combine_priority = combine_priority | ||
| assert_source_provided | ||
@@ -68,4 +76,11 @@ assert_location_provided | ||
| # @return [Pin::Closure, nil] | ||
| def closure | ||
| Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure | ||
| # @type [Pin::Closure, nil] | ||
| @closure | ||
| end | ||
| # @param other [self] | ||
| # @param attrs [Hash{Symbol => Object}] | ||
| # @param attrs [Hash{::Symbol => Object}] | ||
| # | ||
@@ -75,2 +90,5 @@ # @return [self] | ||
| raise "tried to combine #{other.class} with #{self.class}" unless other.class == self.class | ||
| priority_choice = choose_priority(other) | ||
| return priority_choice unless priority_choice.nil? | ||
| type_location = choose(other, :type_location) | ||
@@ -88,2 +106,3 @@ location = choose(other, :location) | ||
| directives: combine_directives(other), | ||
| combine_priority: combine_priority | ||
| }.merge(attrs) | ||
@@ -98,2 +117,21 @@ assert_same_macros(other) | ||
| # @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) | ||
| if combine_priority.nil? && !other.combine_priority.nil? | ||
| return other | ||
| elsif other.combine_priority.nil? && !combine_priority.nil? | ||
| return self | ||
| elsif !combine_priority.nil? && !other.combine_priority.nil? | ||
| if combine_priority > other.combine_priority | ||
| return self | ||
| elsif combine_priority < other.combine_priority | ||
| return other | ||
| end | ||
| end | ||
| nil | ||
| end | ||
| # @param other [self] | ||
| # @param attr [::Symbol] | ||
@@ -109,3 +147,2 @@ # @sg-ignore | ||
| return val2 if val1.nil? | ||
| # @sg-ignore | ||
| val1.length > val2.length ? val1 : val2 | ||
@@ -200,3 +237,3 @@ end | ||
| # @param other [self] | ||
| # @param attr [Symbol] | ||
| # @param attr [::Symbol] | ||
| # @sg-ignore | ||
@@ -250,3 +287,2 @@ # @return [undefined] | ||
| # @sg-ignore | ||
| # @type [undefined] | ||
@@ -283,3 +319,4 @@ values1 = arr1.map(&block) | ||
| # | ||
| # @return [Object, nil] | ||
| # @sg-ignore | ||
| # @return [undefined] | ||
| def assert_same(other, attr) | ||
@@ -315,2 +352,4 @@ return false if other.nil? | ||
| # @param attr [::Symbol] | ||
| # | ||
| # @sg-ignore | ||
| # @return [undefined] | ||
@@ -323,7 +362,10 @@ def choose_pin_attr(other, attr) | ||
| if val1.class != val2.class | ||
| # :nocov: | ||
| Solargraph.assert_or_log("combine_with_#{attr}_class".to_sym, | ||
| "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: | ||
| end | ||
| # arbitrary way of choosing a pin | ||
| # @sg-ignore Need _1 support | ||
| [val1, val2].compact.min_by { _1.best_location.to_s } | ||
@@ -330,0 +372,0 @@ end |
@@ -24,2 +24,3 @@ # frozen_string_literal: true | ||
| # @return [String] | ||
| def method_namespace | ||
@@ -29,2 +30,4 @@ closure.namespace | ||
| # @param other [self] | ||
| # @return [Pin::Block, nil] | ||
| def combine_blocks(other) | ||
@@ -62,2 +65,4 @@ if block.nil? | ||
| # @param other [self] | ||
| # @return [Array<Pin::Parameter>] | ||
| def choose_parameters(other) | ||
@@ -76,2 +81,3 @@ 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 | ||
| # @return [Array<Pin::Parameter>] | ||
| def blockless_parameters | ||
@@ -85,2 +91,3 @@ if parameters.last&.block? | ||
| # @return [Array] | ||
| def arity | ||
@@ -133,2 +140,3 @@ [generics, blockless_parameters.map(&:arity_decl), block&.arity] | ||
| # @return [String] | ||
| def method_name | ||
@@ -206,2 +214,3 @@ raise "closure was nil in #{self.inspect}" if closure.nil? | ||
| # @return [Integer] | ||
| def mandatory_positional_param_count | ||
@@ -208,0 +217,0 @@ parameters.count(&:arg?) |
@@ -11,2 +11,3 @@ # frozen_string_literal: true | ||
| # @param generics [::Array<Pin::Parameter>, nil] | ||
| # @param generic_defaults [Hash{String => ComplexType}] | ||
| def initialize scope: :class, generics: nil, generic_defaults: {}, **splat | ||
@@ -19,2 +20,3 @@ super(**splat) | ||
| # @return [Hash{String => ComplexType}] | ||
| def generic_defaults | ||
@@ -21,0 +23,0 @@ @generic_defaults ||= {} |
@@ -6,8 +6,12 @@ # frozen_string_literal: true | ||
| module Common | ||
| # @!method source | ||
| # @abstract | ||
| # @return [Source, nil] | ||
| # @type @closure [Pin::Closure, nil] | ||
| # @return [Location] | ||
| attr_reader :location | ||
| # @sg-ignore Solargraph::Pin::Common#closure return type could not be inferred | ||
| # @return [Pin::Closure, nil] | ||
| attr_reader :closure | ||
| def closure | ||
@@ -14,0 +18,0 @@ Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure |
@@ -8,2 +8,4 @@ # frozen_string_literal: true | ||
| # @param visibility [::Symbol] The visibility of the constant (:public, :protected, or :private) | ||
| # @param splat [Hash] Additional options supported by superclasses | ||
| def initialize visibility: :public, **splat | ||
@@ -10,0 +12,0 @@ super(**splat) |
@@ -54,2 +54,3 @@ # frozen_string_literal: true | ||
| define_method(method) do |api_map| | ||
| # @sg-ignore Unresolved call to resolve_method | ||
| resolve_method(api_map) | ||
@@ -56,0 +57,0 @@ # @sg-ignore Need to set context correctly in define_method blocks |
@@ -29,3 +29,6 @@ # frozen_string_literal: true | ||
| }.merge(attrs) | ||
| new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) | ||
| # @sg-ignore Wrong argument type for | ||
| # Solargraph::Pin::Base#assert_same: other expected | ||
| # Solargraph::Pin::Base, received self | ||
| new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) | ||
@@ -32,0 +35,0 @@ super(other, new_attrs) |
@@ -16,2 +16,5 @@ # frozen_string_literal: true | ||
| # @param scope [::Symbol] | ||
| # @param original [String, nil] The name of the original method | ||
| # @param splat [Hash] Additional options supported by superclasses | ||
| def initialize scope: :instance, original: nil, **splat | ||
@@ -18,0 +21,0 @@ super(**splat) |
@@ -25,3 +25,4 @@ # frozen_string_literal: true | ||
| # @param anon_splat [Boolean] | ||
| def initialize visibility: :public, explicit: true, block: :undefined, node: nil, attribute: false, signatures: nil, anon_splat: false, **splat | ||
| def initialize visibility: :public, explicit: true, block: :undefined, node: nil, attribute: false, signatures: nil, anon_splat: false, | ||
| **splat | ||
| super(**splat) | ||
@@ -54,3 +55,3 @@ @visibility = visibility | ||
| # @param other [Pin::Method] | ||
| # @return [Symbol] | ||
| # @return [::Symbol] | ||
| def combine_visibility(other) | ||
@@ -81,2 +82,5 @@ if dodgy_visibility_source? && !other.dodgy_visibility_source? | ||
| def combine_with(other, attrs = {}) | ||
| priority_choice = choose_priority(other) | ||
| return priority_choice unless priority_choice.nil? | ||
| sigs = combine_signatures(other) | ||
@@ -307,3 +311,2 @@ parameters = if sigs.length > 0 | ||
| # @sg-ignore | ||
| def documentation | ||
@@ -432,3 +435,3 @@ if @documentation.nil? | ||
| docstring.ref_tags.each do |tag| | ||
| ref = if tag.owner.to_s.start_with?(/[#\.]/) | ||
| ref = if tag.owner.to_s.start_with?(/[#.]/) | ||
| api_map.get_methods(namespace) | ||
@@ -557,3 +560,3 @@ .select { |pin| pin.path.end_with?(tag.owner.to_s) } | ||
| def resolve_reference ref, api_map | ||
| parts = ref.split(/[\.#]/) | ||
| parts = ref.split(/[.#]/) | ||
| if parts.first.empty? || parts.one? | ||
@@ -560,0 +563,0 @@ path = "#{namespace}#{ref}" |
@@ -108,11 +108,9 @@ # frozen_string_literal: true | ||
| # @return [String] | ||
| def full | ||
| # @return [String] the full name of the parameter, including any | ||
| # declarative symbols such as `*` or `:` indicating type of | ||
| # parameter. This is used in method signatures. | ||
| def full_name | ||
| case decl | ||
| when :optarg | ||
| "#{name} = #{asgn_code || '?'}" | ||
| when :kwarg | ||
| when :kwarg, :kwoptarg | ||
| "#{name}:" | ||
| when :kwoptarg | ||
| "#{name}: #{asgn_code || '?'}" | ||
| when :restarg | ||
@@ -129,2 +127,14 @@ "*#{name}" | ||
| # @return [String] | ||
| def full | ||
| full_name + case decl | ||
| when :optarg | ||
| " = #{asgn_code || '?'}" | ||
| when :kwoptarg | ||
| " #{asgn_code || '?'}" | ||
| else | ||
| '' | ||
| end | ||
| end | ||
| # @return [ComplexType] | ||
@@ -242,3 +252,3 @@ def return_type | ||
| skip.push ref | ||
| parts = ref.split(/[\.#]/) | ||
| parts = ref.split(/[.#]/) | ||
| if parts.first.empty? | ||
@@ -245,0 +255,0 @@ path = "#{namespace}#{ref}" |
@@ -7,2 +7,3 @@ # frozen_string_literal: true | ||
| # @param return_type [ComplexType] | ||
| # @param binder [ComplexType, ComplexType::UniqueType, nil] | ||
| def initialize return_type: ComplexType::UNDEFINED, binder: nil, **splat | ||
@@ -9,0 +10,0 @@ super(**splat) |
@@ -20,4 +20,30 @@ # frozen_string_literal: true | ||
| end | ||
| # @return [String] | ||
| def parameter_tag | ||
| @parameter_tag ||= if generic_values&.any? | ||
| "<#{generic_values.join(', ')}>" | ||
| else | ||
| '' | ||
| end | ||
| end | ||
| # @return [ComplexType] | ||
| def parametrized_tag | ||
| @parametrized_tag ||= ComplexType.try_parse( | ||
| name + | ||
| if generic_values&.length&.> 0 | ||
| "<#{generic_values.join(', ')}>" | ||
| else | ||
| '' | ||
| end | ||
| ) | ||
| end | ||
| # @return [Array<String>] | ||
| def reference_gates | ||
| closure.gates | ||
| end | ||
| end | ||
| end | ||
| end |
@@ -17,2 +17,7 @@ # frozen_string_literal: true | ||
| # @param location [Location, nil] | ||
| # @param name [String] | ||
| # @param tags [::Array<YARD::Tags::Tag>] | ||
| # @param delete [::Array<Symbol>] | ||
| # @param splat [Hash] | ||
| def initialize location, name, tags, delete = [], **splat | ||
@@ -24,6 +29,15 @@ super(location: location, name: name, **splat) | ||
| # @param name [String] | ||
| # @param tags [::Array<String>] | ||
| # @param delete [::Array<Symbol>] | ||
| # @param splat [Hash] | ||
| # @return [Solargraph::Pin::Reference::Override] | ||
| def self.method_return name, *tags, delete: [], **splat | ||
| new(nil, name, [YARD::Tags::Tag.new('return', nil, tags)], delete, **splat) | ||
| new(nil, name, [YARD::Tags::Tag.new('return', '', tags)], delete, **splat) | ||
| end | ||
| # @param name [String] | ||
| # @param comment [String] | ||
| # @param splat [Hash] | ||
| # @return [Solargraph::Pin::Reference::Override] | ||
| def self.from_comment name, comment, **splat | ||
@@ -30,0 +44,0 @@ new(nil, name, Solargraph::Source.parse_docstring(comment).to_docstring.tags, **splat) |
@@ -6,3 +6,8 @@ # frozen_string_literal: true | ||
| class Reference | ||
| # A Superclass reference pin. | ||
| # | ||
| class Superclass < Reference | ||
| def reference_gates | ||
| @reference_gates ||= closure.gates - [closure.path] | ||
| end | ||
| end | ||
@@ -9,0 +14,0 @@ end |
@@ -15,2 +15,4 @@ # frozen_string_literal: true | ||
| # @param match [Float] The match score for the pin | ||
| # @param pin [Pin::Base] | ||
| def initialize match, pin | ||
@@ -52,3 +54,3 @@ @match = match | ||
| 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.to_f) if str1.downcase.include?(str2.downcase) | ||
| JaroWinkler.similarity(str1, str2, ignore_case: true) | ||
@@ -55,0 +57,0 @@ end |
@@ -42,2 +42,4 @@ module Solargraph | ||
| return ComplexType::UNDEFINED unless closure.is_a?(Pin::Method) | ||
| # @sg-ignore need is_a? support | ||
| # @type [Array<Pin::Method>] | ||
| method_stack = closure.rest_of_stack api_map | ||
@@ -44,0 +46,0 @@ logger.debug { "Signature#typify(self=#{self}) - method_stack: #{method_stack}" } |
@@ -23,2 +23,6 @@ # frozen_string_literal: true | ||
| def closure | ||
| @closure ||= Pin::ROOT_PIN | ||
| end | ||
| def completion_item_kind | ||
@@ -40,2 +44,3 @@ Solargraph::LanguageServer::CompletionItemKinds::KEYWORD | ||
| # @return [::Symbol] | ||
| def visibility | ||
@@ -42,0 +47,0 @@ :public |
@@ -29,2 +29,3 @@ # frozen_string_literal: true | ||
| # @param other [Position] | ||
| def <=>(other) | ||
@@ -61,2 +62,3 @@ return nil unless other.is_a?(Position) | ||
| return 0 if text.empty? | ||
| # @sg-ignore Unresolved call to + on Integer | ||
| text.lines[0...position.line].sum(&:length) + position.character | ||
@@ -63,0 +65,0 @@ end |
@@ -27,2 +27,3 @@ # frozen_string_literal: true | ||
| # @param other [BasicObject] | ||
| def <=>(other) | ||
@@ -29,0 +30,0 @@ return nil unless other.is_a?(Range) |
| # frozen_string_literal: true | ||
| require 'digest' | ||
| require 'pathname' | ||
@@ -25,3 +26,4 @@ require 'rbs' | ||
| # @param library [String] | ||
| # @param version [String, nil] | ||
| # @param version [String, nil | ||
| # @param rbs_collection_config_path [String, Pathname, nil] | ||
| # @param rbs_collection_paths [Array<Pathname, String>] | ||
@@ -39,2 +41,3 @@ def initialize library, version = nil, rbs_collection_config_path: nil, rbs_collection_paths: [] | ||
| # @return [RBS::EnvironmentLoader] | ||
| def loader | ||
@@ -44,3 +47,3 @@ @loader ||= RBS::EnvironmentLoader.new(core_root: nil, repository: repository) | ||
| # @return string representing the version of the RBS info fetched | ||
| # @return [String] representing the version of the RBS info fetched | ||
| # for the given library. Must change when the RBS info is | ||
@@ -51,2 +54,3 @@ # updated upstream for the same library and version. May change | ||
| @hextdigest ||= begin | ||
| # @type [String, nil] | ||
| data = nil | ||
@@ -75,2 +79,6 @@ if rbs_collection_config_path | ||
| # @param gemspec [Gem::Specification] | ||
| # @param rbs_collection_path [String, Pathname, nil] | ||
| # @param rbs_collection_config_path [String, Pathname, nil] | ||
| # @return [RbsMap] | ||
| def self.from_gemspec gemspec, rbs_collection_path, rbs_collection_config_path | ||
@@ -88,2 +96,3 @@ rbs_map = RbsMap.new(gemspec.name, gemspec.version, | ||
| # @return [Array<Pin::Base>] | ||
| def pins | ||
@@ -112,2 +121,3 @@ @pins ||= resolved? ? conversions.pins : [] | ||
| # @return [RBS::Repository] | ||
| def repository | ||
@@ -130,2 +140,3 @@ @repository ||= RBS::Repository.new(no_stdlib: false).tap do |repo| | ||
| # @return [RBS::EnvironmentLoader] | ||
| def loader | ||
@@ -135,2 +146,3 @@ @loader ||= RBS::EnvironmentLoader.new(core_root: nil, repository: repository) | ||
| # @return [Conversions] | ||
| def conversions | ||
@@ -142,2 +154,3 @@ @conversions ||= Conversions.new(loader: loader) | ||
| # @param library [String] | ||
| # @param version [String, nil] | ||
| # @return [Boolean] true if adding the library succeeded | ||
@@ -144,0 +157,0 @@ def add_library loader, library, version |
@@ -166,5 +166,6 @@ # frozen_string_literal: true | ||
| end | ||
| class_name = decl.name.relative!.to_s | ||
| class_pin = Solargraph::Pin::Namespace.new( | ||
| type: :class, | ||
| name: decl.name.relative!.to_s, | ||
| name: class_name, | ||
| closure: Solargraph::Pin::ROOT_PIN, | ||
@@ -184,2 +185,3 @@ comments: decl.comment&.string, | ||
| generic_values = type.all_params.map(&:to_s) | ||
| superclass_name = decl.super_class.name.to_s | ||
| pins.push Solargraph::Pin::Reference::Superclass.new( | ||
@@ -189,3 +191,3 @@ type_location: location_decl_to_pin_location(decl.super_class.location), | ||
| generic_values: generic_values, | ||
| name: decl.super_class.name.relative!.to_s, | ||
| name: superclass_name, | ||
| source: :rbs | ||
@@ -351,3 +353,3 @@ ) | ||
| # @param decl [RBS::AST::Members::MethodDefinition, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrAccessor] | ||
| # @param closure [Pin::Namespace] | ||
| # @param closure [Pin::Closure] | ||
| # @param context [Context] | ||
@@ -706,3 +708,3 @@ # @param scope [Symbol] :instance or :class | ||
| 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) }.reject { |t| t == 'undefined' }.map do |t| | ||
| params = type_args.map { |a| other_type_to_tag(a) }.map do |t| | ||
| ComplexType.try_parse(t).force_rooted | ||
@@ -713,3 +715,3 @@ end | ||
| else | ||
| ComplexType::UniqueType.new(base, [], params, rooted: true, parameters_type: :list) | ||
| ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) | ||
| end | ||
@@ -716,0 +718,0 @@ end |
@@ -17,2 +17,3 @@ # frozen_string_literal: true | ||
| # @return [Enumerable<Pin::Base>] | ||
| def pins | ||
@@ -43,2 +44,3 @@ return @pins if @pins | ||
| # @return [RBS::EnvironmentLoader] | ||
| def loader | ||
@@ -48,2 +50,3 @@ @loader ||= RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) | ||
| # @return [Conversions] | ||
| def conversions | ||
@@ -50,0 +53,0 @@ @conversions ||= Conversions.new(loader: loader) |
@@ -39,2 +39,3 @@ # frozen_string_literal: true | ||
| end | ||
| # @sg-ignore Wrong argument type for Backport.prepare_tcp_server: adapter expected Backport::Adapter, received Module<Solargraph::LanguageServer::Transport::Adapter> | ||
| Backport.prepare_tcp_server host: options[:host], port: port, adapter: Solargraph::LanguageServer::Transport::Adapter | ||
@@ -56,2 +57,3 @@ STDERR.puts "Solargraph is listening PORT=#{port} PID=#{Process.pid}" | ||
| end | ||
| # @sg-ignore Wrong argument type for Backport.prepare_stdio_server: adapter expected Backport::Adapter, received Module<Solargraph::LanguageServer::Transport::Adapter> | ||
| Backport.prepare_stdio_server adapter: Solargraph::LanguageServer::Transport::Adapter | ||
@@ -263,2 +265,3 @@ STDERR.puts "Solargraph is listening on stdio PID=#{Process.pid}" | ||
| # @param gemspec [Gem::Specification] | ||
| # @param api_map [ApiMap] | ||
| # @return [void] | ||
@@ -265,0 +268,0 @@ def do_cache gemspec, api_map |
@@ -24,2 +24,7 @@ # frozen_string_literal: true | ||
| # @return [Array<Pin::Base>] | ||
| def all_pins | ||
| pins + convention_pins | ||
| end | ||
| # @return [Array<Pin::LocalVariable>] | ||
@@ -34,9 +39,14 @@ def locals | ||
| environ.merge Convention.for_local(self) unless filename.nil? | ||
| self.convention_pins = environ.pins | ||
| conventions_environ.merge Convention.for_local(self) unless filename.nil? | ||
| # FIXME: unmemoizing the document_symbols in case it was called and memoized from any of conventions above | ||
| # this is to ensure that the convention_pins from all conventions are used in the document_symbols. | ||
| # solargraph-rails is known to use this method to get the document symbols. It should probably be removed. | ||
| @document_symbols = nil | ||
| self.convention_pins = conventions_environ.pins | ||
| @pin_select_cache = {} | ||
| end | ||
| # @param klass [Class] | ||
| # @return [Array<Pin::Base>] | ||
| # @generic T | ||
| # @param klass [Class<generic<T>>] | ||
| # @return [Array<generic<T>>] | ||
| def pins_by_class klass | ||
@@ -72,4 +82,4 @@ @pin_select_cache[klass] ||= pin_class_hash.select { |key, _| key <= klass }.values.flatten | ||
| # @return [Environ] | ||
| def environ | ||
| @environ ||= Environ.new | ||
| def conventions_environ | ||
| @conventions_environ ||= Environ.new | ||
| end | ||
@@ -164,2 +174,6 @@ | ||
| # @return [Hash{Class => Array<Pin::Base>}] | ||
| # @return [Array<Pin::Base>] | ||
| attr_writer :convention_pins | ||
| def pin_class_hash | ||
@@ -169,2 +183,3 @@ @pin_class_hash ||= pins.to_set.classify(&:class).transform_values(&:to_a) | ||
| # @return [Data] | ||
| def data | ||
@@ -179,10 +194,2 @@ @data ||= Data.new(source) | ||
| # @param pins [Array<Pin::Base>] | ||
| # @return [Array<Pin::Base>] | ||
| def convention_pins=(pins) | ||
| # unmemoizing the document_symbols in case it was called from any of conventions | ||
| @document_symbols = nil | ||
| @convention_pins = pins | ||
| end | ||
| # @param line [Integer] | ||
@@ -189,0 +196,0 @@ # @param character [Integer] |
@@ -68,3 +68,3 @@ # frozen_string_literal: true | ||
| # | ||
| # @return [::Array<Solargraph::Pin::Base>] | ||
| # @return [::Array<Solargraph::Pin::LocalVariable>] | ||
| def locals | ||
@@ -71,0 +71,0 @@ @locals ||= source_map.locals_at(location) |
@@ -6,2 +6,3 @@ # frozen_string_literal: true | ||
| class Data | ||
| # @param source [Solargraph::Source] | ||
| def initialize source | ||
@@ -11,2 +12,3 @@ @source = source | ||
| # @return [Array<Solargraph::Pin::Base>] | ||
| def pins | ||
@@ -17,2 +19,3 @@ generate | ||
| # @return [Array<Solargraph::LocalVariable>] | ||
| def locals | ||
@@ -25,2 +28,3 @@ generate | ||
| # @return [void] | ||
| def generate | ||
@@ -27,0 +31,0 @@ return if @generated |
@@ -73,2 +73,3 @@ # frozen_string_literal: true | ||
| def process_comment source_position, comment_position, comment | ||
| # @sg-ignore Wrong argument type for String#=~: object expected String::_MatchAgainst<String, undefined>, received Regexp | ||
| return unless comment.encode('UTF-8', invalid: :replace, replace: '?') =~ DIRECTIVE_REGEXP | ||
@@ -167,3 +168,3 @@ cmnt = remove_inline_comment_hashes(comment) | ||
| when 'visibility' | ||
| begin | ||
| kind = directive.tag.text&.to_sym | ||
@@ -187,3 +188,3 @@ return unless [:private, :protected, :public].include?(kind) | ||
| end | ||
| end | ||
| when 'parse' | ||
@@ -250,2 +251,3 @@ begin | ||
| def process_comment_directives | ||
| # @sg-ignore Wrong argument type for String#=~: object expected String::_MatchAgainst<String, undefined>, received Regexp | ||
| return unless @code.encode('UTF-8', invalid: :replace, replace: '?') =~ DIRECTIVE_REGEXP | ||
@@ -252,0 +254,0 @@ code_lines = @code.lines |
@@ -33,3 +33,3 @@ # frozen_string_literal: true | ||
| # @return [Hash{Integer => Array<String>}] | ||
| # @return [Hash{Integer => Solargraph::Parser::Snippet}] | ||
| def comments | ||
@@ -239,2 +239,3 @@ finalize | ||
| @associated_comments ||= begin | ||
| # @type [Hash{Integer => String}] | ||
| result = {} | ||
@@ -322,3 +323,3 @@ buffer = String.new('') | ||
| # @return [Array<Range>] | ||
| # @return [Array<Solargraph::Range>] | ||
| def comment_ranges | ||
@@ -392,2 +393,3 @@ @comment_ranges ||= comments.values.map(&:range) | ||
| # @return [void] | ||
| def finalize | ||
@@ -447,2 +449,3 @@ return if @finalized && changes.empty? | ||
| # @return [String] | ||
| def repaired | ||
@@ -449,0 +452,0 @@ finalize |
@@ -18,2 +18,3 @@ # frozen_string_literal: true | ||
| include Equality | ||
| include Logging | ||
@@ -79,3 +80,5 @@ autoload :Link, 'solargraph/source/chain/link' | ||
| # | ||
| # @param api_map [ApiMap] @param name_pin [Pin::Base] A pin | ||
| # @param api_map [ApiMap] | ||
| # | ||
| # @param name_pin [Pin::Base] A pin | ||
| # representing the place in which expression is evaluated (e.g., | ||
@@ -197,2 +200,3 @@ # a Method pin, or a Module or Class pin if not run within a | ||
| # @return [String] | ||
| def desc | ||
@@ -271,3 +275,6 @@ links.map(&:desc).to_s | ||
| end | ||
| return type if context.nil? || context.return_type.undefined? | ||
| if context.nil? || context.return_type.undefined? | ||
| # up to downstream to resolve self type | ||
| return type | ||
| end | ||
@@ -274,0 +281,0 @@ type.self_to_type(context.return_type) |
@@ -20,3 +20,9 @@ # frozen_string_literal: true | ||
| # @sg-ignore Fix "Not enough arguments to Module#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 | ||
| protected def equality_fields | ||
@@ -43,2 +49,3 @@ [self.class, word] | ||
| # debugging description of contents; not for machine use | ||
| # @return [String] | ||
| def desc | ||
@@ -79,2 +86,4 @@ word | ||
| # debugging description of contents; not for machine use | ||
| # | ||
| # @return [String] | ||
| def desc | ||
@@ -81,0 +90,0 @@ word |
@@ -31,3 +31,3 @@ # frozen_string_literal: true | ||
| def write text, nullable = false | ||
| if nullable and !range.nil? and new_text.match(/[\.\[\{\(@\$:]$/) | ||
| if nullable and !range.nil? and new_text.match(/[.\[{(@$:]$/) | ||
| [':', '@'].each do |dupable| | ||
@@ -63,3 +63,3 @@ next unless new_text == dupable | ||
| off = Position.to_offset(text, range.start) | ||
| match = result[0, off].match(/[\.:]+\z/) | ||
| match = result[0, off].match(/[.:]+\z/) | ||
| if match | ||
@@ -66,0 +66,0 @@ result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..-1] |
@@ -38,3 +38,2 @@ # frozen_string_literal: true | ||
| # | ||
| # @sg-ignore Improve resolution of String#match below | ||
| # @return [String] | ||
@@ -129,3 +128,3 @@ def start_of_word | ||
| if start_of_word.empty? | ||
| match = source.code[0, offset].match(/[\s]*(\.|:+)[\s]*$/) | ||
| match = source.code[0, offset].match(/\s*(\.|:+)\s*$/) | ||
| if match | ||
@@ -165,3 +164,3 @@ Position.from_offset(source.code, offset - match[0].length) | ||
| def end_word_pattern | ||
| /^([a-z0-9_]|[^\u0000-\u007F])*[\?\!]?/i | ||
| /^([a-z0-9_]|[^\u0000-\u007F])*[?!]?/i | ||
| end | ||
@@ -168,0 +167,0 @@ end |
@@ -100,3 +100,3 @@ # frozen_string_literal: true | ||
| @end_of_phrase ||= begin | ||
| match = phrase.match(/[\s]*(\.{1}|::)[\s]*$/) | ||
| match = phrase.match(/\s*(\.{1}|::)\s*$/) | ||
| if match | ||
@@ -103,0 +103,0 @@ match[0] |
+169
-116
@@ -41,11 +41,16 @@ # frozen_string_literal: true | ||
| # @return [Source] | ||
| def source | ||
| @source_map.source | ||
| end | ||
| # @return [Array<Problem>] | ||
| def problems | ||
| @problems ||= begin | ||
| without_ignored( | ||
| method_tag_problems | ||
| .concat variable_type_tag_problems | ||
| .concat const_problems | ||
| .concat call_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 | ||
@@ -144,3 +149,3 @@ end | ||
| def virtual_pin? pin | ||
| pin.location && source_map.source.comment_at?(pin.location.range.ending) | ||
| pin.location && source.comment_at?(pin.location.range.ending) | ||
| end | ||
@@ -236,3 +241,3 @@ | ||
| result = [] | ||
| Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const| | ||
| Solargraph::Parser::NodeMethods.const_nodes_from(source.node).each do |const| | ||
| rng = Solargraph::Range.from_node(const) | ||
@@ -255,3 +260,3 @@ chain = Solargraph::Parser.chain(const, filename) | ||
| result = [] | ||
| Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call| | ||
| Solargraph::Parser::NodeMethods.call_nodes_from(source.node).each do |call| | ||
| rng = Solargraph::Range.from_node(call) | ||
@@ -279,3 +284,7 @@ next if @marked_ranges.any? { |d| d.contain?(rng.start) } | ||
| unless closest.generic? || ignored_pins.include?(found) | ||
| result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}") | ||
| 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 | ||
@@ -292,123 +301,132 @@ end | ||
| # @param api_map [Solargraph::ApiMap] | ||
| # @param block_pin [Solargraph::Pin::Base] | ||
| # @param closure_pin [Solargraph::Pin::Closure] | ||
| # @param locals [Array<Solargraph::Pin::Base>] | ||
| # @param location [Solargraph::Location] | ||
| # @return [Array<Problem>] | ||
| def argument_problems_for chain, api_map, block_pin, locals, location | ||
| def argument_problems_for chain, api_map, closure_pin, locals, location | ||
| result = [] | ||
| base = chain | ||
| until base.links.length == 1 && base.undefined? | ||
| last_base_link = base.links.last | ||
| break unless last_base_link.is_a?(Solargraph::Source::Chain::Call) | ||
| # @type last_base_link [Solargraph::Source::Chain::Call] | ||
| last_base_link = base.links.last | ||
| return [] unless last_base_link.is_a?(Solargraph::Source::Chain::Call) | ||
| arguments = last_base_link.arguments | ||
| arguments = last_base_link.arguments | ||
| pins = base.define(api_map, block_pin, locals) | ||
| pins = base.define(api_map, closure_pin, locals) | ||
| first_pin = pins.first | ||
| if first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map) | ||
| # Do nothing, as we can't find the actual method implementation | ||
| elsif first_pin.is_a?(Pin::Method) | ||
| # @type [Pin::Method] | ||
| pin = first_pin | ||
| ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper) | ||
| arity_problems_for(pin, fake_args_for(block_pin), location) | ||
| elsif pin.path == 'Class#new' | ||
| fqns = if base.links.one? | ||
| block_pin.namespace | ||
| first_pin = pins.first | ||
| unresolvable = first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map) | ||
| if !unresolvable && first_pin.is_a?(Pin::Method) | ||
| # @type [Pin::Method] | ||
| pin = first_pin | ||
| ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper) | ||
| arity_problems_for(pin, fake_args_for(closure_pin), location) | ||
| elsif pin.path == 'Class#new' | ||
| fqns = if base.links.one? | ||
| closure_pin.namespace | ||
| else | ||
| base.base.infer(api_map, closure_pin, locals).namespace | ||
| end | ||
| init = api_map.get_method_stack(fqns, 'initialize').first | ||
| init ? arity_problems_for(init, arguments, location) : [] | ||
| else | ||
| arity_problems_for(pin, arguments, location) | ||
| end | ||
| return ap unless ap.empty? | ||
| return [] if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper) | ||
| params = first_param_hash(pins) | ||
| all_errors = [] | ||
| pin.signatures.sort { |sig| sig.parameters.length }.each do |sig| | ||
| signature_errors = signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin | ||
| if signature_errors.empty? | ||
| # we found a signature that works - meaning errors from | ||
| # other signatures don't matter. | ||
| return [] | ||
| end | ||
| all_errors.concat signature_errors | ||
| end | ||
| result.concat all_errors | ||
| end | ||
| result | ||
| end | ||
| # @param location [Location] | ||
| # @param locals [Array<Pin::LocalVariable>] | ||
| # @param closure_pin [Pin::Closure] | ||
| # @param params [Hash{String => Hash{Symbol => String, Solargraph::ComplexType}}] | ||
| # @param arguments [Array<Source::Chain>] | ||
| # @param sig [Pin::Signature] | ||
| # @param pin [Pin::Method] | ||
| # @param pins [Array<Pin::Method>] | ||
| # | ||
| # @return [Array<Problem>] | ||
| def signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin | ||
| errors = [] | ||
| # @todo add logic mapping up restarg parameters with | ||
| # arguments (including restarg arguments). Use tuples | ||
| # 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 | ||
| argchain = arguments[idx] | ||
| if argchain.nil? | ||
| if par.decl == :arg | ||
| final_arg = arguments.last | ||
| if final_arg && final_arg.node.type == :splat | ||
| argchain = final_arg | ||
| return errors | ||
| else | ||
| base.base.infer(api_map, block_pin, locals).namespace | ||
| errors.push Problem.new(location, "Not enough arguments to #{pin.path}") | ||
| end | ||
| init = api_map.get_method_stack(fqns, 'initialize').first | ||
| init ? arity_problems_for(init, arguments, location) : [] | ||
| else | ||
| arity_problems_for(pin, arguments, location) | ||
| final_arg = arguments.last | ||
| argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type) | ||
| end | ||
| unless ap.empty? | ||
| result.concat ap | ||
| break | ||
| end | ||
| break if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper) | ||
| 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) | ||
| # 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 | ||
| # continue on in case there are any required | ||
| # kwargs we should warn about | ||
| next | ||
| end | ||
| if argchain.node.type == :splat && par != sig.parameters.last | ||
| # we have been given a splat and there are more | ||
| # arguments to come. | ||
| params = first_param_hash(pins) | ||
| all_errors = [] | ||
| pin.signatures.sort { |sig| sig.parameters.length }.each do |sig| | ||
| errors = [] | ||
| sig.parameters.each_with_index do |par, idx| | ||
| # @todo add logic mapping up restarg parameters with | ||
| # arguments (including restarg arguments). Use tuples | ||
| # when possible, and when not, ensure provably | ||
| # incorrect situations are detected. | ||
| break if par.decl == :restarg # bail out pending better arg processing | ||
| argchain = arguments[idx] | ||
| if argchain.nil? | ||
| if par.decl == :arg | ||
| final_arg = arguments.last | ||
| if final_arg && final_arg.node.type == :splat | ||
| argchain = final_arg | ||
| next # don't try to apply the type of the splat - unlikely to be specific enough | ||
| else | ||
| errors.push Problem.new(location, "Not enough arguments to #{pin.path}") | ||
| next | ||
| end | ||
| else | ||
| final_arg = arguments.last | ||
| argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type) | ||
| end | ||
| # @todo Improve this so that we can skip past the | ||
| # rest of the positional parameters here but still | ||
| # process the kwargs | ||
| return errors | ||
| end | ||
| ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED | ||
| ptype = ptype.self_to_type(par.context) | ||
| if ptype.nil? | ||
| # @todo Some level (strong, I guess) should require the param here | ||
| else | ||
| argtype = argchain.infer(api_map, closure_pin, locals) | ||
| if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype) | ||
| errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") | ||
| return errors | ||
| end | ||
| if argchain | ||
| if par.decl != :arg | ||
| errors.concat kwarg_problems_for sig, argchain, api_map, block_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) | ||
| # 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 | ||
| # continue on in case there are any required | ||
| # kwargs we should warn about | ||
| next | ||
| end | ||
| if argchain.node.type == :splat && par != sig.parameters.last | ||
| # we have been given a splat and there are more | ||
| # arguments to come. | ||
| # @todo Improve this so that we can skip past the | ||
| # rest of the positional parameters here but still | ||
| # process the kwargs | ||
| break | ||
| end | ||
| ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED | ||
| ptype = ptype.self_to_type(par.context) | ||
| if ptype.nil? | ||
| # @todo Some level (strong, I guess) should require the param here | ||
| else | ||
| argtype = argchain.infer(api_map, block_pin, locals) | ||
| if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype) | ||
| errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") | ||
| next | ||
| end | ||
| end | ||
| end | ||
| elsif par.decl == :kwarg | ||
| errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") | ||
| next | ||
| end | ||
| end | ||
| if errors.empty? | ||
| all_errors.clear | ||
| break | ||
| end | ||
| all_errors.concat errors | ||
| end | ||
| result.concat all_errors | ||
| elsif par.decl == :kwarg | ||
| errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") | ||
| next | ||
| end | ||
| base = base.base | ||
| end | ||
| result | ||
| errors | ||
| end | ||
@@ -655,3 +673,2 @@ | ||
| # signature and generate Integer as return type | ||
| # @sg-ignore | ||
| # @return [Integer] | ||
@@ -697,2 +714,31 @@ def required_param_count(parameters) | ||
| # @return [Set<Integer>] | ||
| def sg_ignore_lines_processed | ||
| @sg_ignore_lines_processed ||= Set.new | ||
| end | ||
| # @return [Set<Integer>] | ||
| def all_sg_ignore_lines | ||
| source.associated_comments.select do |_line, text| | ||
| text.include?('@sg-ignore') | ||
| end.keys.to_set | ||
| end | ||
| # @return [Array<Integer>] | ||
| def unprocessed_sg_ignore_lines | ||
| (all_sg_ignore_lines - sg_ignore_lines_processed).to_a.sort | ||
| end | ||
| # @return [Array<Problem>] | ||
| def unneeded_sgignore_problems | ||
| return [] unless rules.validate_sg_ignores? | ||
| unprocessed_sg_ignore_lines.map do |line| | ||
| Problem.new( | ||
| Location.new(filename, Range.from_to(line, 0, line, 0)), | ||
| 'Unneeded @sg-ignore comment' | ||
| ) | ||
| end | ||
| end | ||
| # @param problems [Array<Problem>] | ||
@@ -702,4 +748,11 @@ # @return [Array<Problem>] | ||
| problems.reject do |problem| | ||
| node = source_map.source.node_at(problem.location.range.start.line, problem.location.range.start.column) | ||
| node && source_map.source.comments_for(node)&.include?('@sg-ignore') | ||
| node = source.node_at(problem.location.range.start.line, problem.location.range.start.column) | ||
| ignored = node && source.comments_for(node)&.include?('@sg-ignore') | ||
| unless !ignored || all_sg_ignore_lines.include?(problem.location.range.start.line) | ||
| # :nocov: | ||
| Solargraph.assert_or_log(:sg_ignore) { "@sg-ignore accounting issue - node is #{node}" } | ||
| # :nocov: | ||
| end | ||
| sg_ignore_lines_processed.add problem.location.range.start.line if ignored | ||
| ignored | ||
| end | ||
@@ -706,0 +759,0 @@ end |
@@ -15,2 +15,4 @@ # frozen_string_literal: true | ||
| # @param name [String] | ||
| # @param type [Symbol] The type of parameter, such as :req, :opt, :rest, etc. | ||
| def initialize name, type | ||
@@ -17,0 +19,0 @@ @name = name |
@@ -60,4 +60,12 @@ # frozen_string_literal: true | ||
| end | ||
| # We keep this at strong because if you added an @ sg-ignore to | ||
| # address a strong-level issue, then ran at a lower level, you'd | ||
| # get a false positive - we don't run stronger level checks than | ||
| # requested for performance reasons | ||
| def validate_sg_ignores? | ||
| rank >= LEVELS[:strong] | ||
| end | ||
| end | ||
| end | ||
| end |
| # frozen_string_literal: true | ||
| module Solargraph | ||
| VERSION = '0.56.2' | ||
| VERSION = '0.57.0' | ||
| end |
@@ -13,2 +13,3 @@ # frozen_string_literal: true | ||
| autoload :Config, 'solargraph/workspace/config' | ||
| autoload :RequirePaths, 'solargraph/workspace/require_paths' | ||
@@ -18,8 +19,3 @@ # @return [String] | ||
| # The require paths associated with the workspace. | ||
| # | ||
| # @return [Array<String>] | ||
| attr_reader :require_paths | ||
| # @return [Array<String>] | ||
| attr_reader :gemnames | ||
@@ -37,6 +33,13 @@ alias source_gems gemnames | ||
| @gemnames = [] | ||
| @require_paths = generate_require_paths | ||
| require_plugins | ||
| end | ||
| # The require paths associated with the workspace. | ||
| # | ||
| # @return [Array<String>] | ||
| def require_paths | ||
| # @todo are the semantics of '*' the same as '', meaning 'don't send back any require paths'? | ||
| @require_paths ||= RequirePaths.new(directory_or_nil, config).generate | ||
| end | ||
| # @return [Solargraph::Workspace::Config] | ||
@@ -139,2 +142,3 @@ def config | ||
| # @return [String, nil] | ||
| def rbs_collection_config_path | ||
@@ -162,2 +166,8 @@ @rbs_collection_config_path ||= begin | ||
| # @return [String, nil] | ||
| def directory_or_nil | ||
| return nil if directory.empty? || directory == '*' | ||
| directory | ||
| end | ||
| # True if the workspace has a root Gemfile. | ||
@@ -200,44 +210,2 @@ # | ||
| # Generate require paths from gemspecs if they exist or assume the default | ||
| # lib directory. | ||
| # | ||
| # @return [Array<String>] | ||
| def generate_require_paths | ||
| return configured_require_paths unless gemspec? | ||
| result = [] | ||
| gemspecs.each do |file| | ||
| base = File.dirname(file) | ||
| # HACK: Evaluating gemspec files violates the goal of not running | ||
| # workspace code, but this is how Gem::Specification.load does it | ||
| # anyway. | ||
| cmd = ['ruby', '-e', "require 'rubygems'; require 'json'; spec = eval(File.read('#{file}'), TOPLEVEL_BINDING, '#{file}'); return unless Gem::Specification === spec; puts({name: spec.name, paths: spec.require_paths}.to_json)"] | ||
| o, e, s = Open3.capture3(*cmd) | ||
| if s.success? | ||
| begin | ||
| hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {} | ||
| next if hash.empty? | ||
| @gemnames.push hash['name'] | ||
| result.concat(hash['paths'].map { |path| File.join(base, path) }) | ||
| rescue StandardError => e | ||
| Solargraph.logger.warn "Error reading #{file}: [#{e.class}] #{e.message}" | ||
| end | ||
| else | ||
| Solargraph.logger.warn "Error reading #{file}" | ||
| Solargraph.logger.warn e | ||
| end | ||
| end | ||
| result.concat(config.require_paths.map { |p| File.join(directory, p) }) | ||
| result.push File.join(directory, 'lib') if result.empty? | ||
| result | ||
| end | ||
| # Get additional require paths defined in the configuration. | ||
| # | ||
| # @return [Array<String>] | ||
| def configured_require_paths | ||
| return ['lib'] if directory.empty? | ||
| return [File.join(directory, 'lib')] if config.require_paths.empty? | ||
| config.require_paths.map { |p| File.join(directory, p) } | ||
| end | ||
| # @return [void] | ||
@@ -244,0 +212,0 @@ def require_plugins |
@@ -93,3 +93,2 @@ # frozen_string_literal: true | ||
| # | ||
| # @sg-ignore pending https://github.com/castwide/solargraph/pull/905 | ||
| # @return [Hash] | ||
@@ -109,3 +108,2 @@ def formatter | ||
| # | ||
| # @sg-ignore pending https://github.com/castwide/solargraph/pull/905 | ||
| # @return [Integer] | ||
@@ -112,0 +110,0 @@ def max_files |
@@ -30,4 +30,4 @@ # frozen_string_literal: true | ||
| final_visibility = VISIBILITY_OVERRIDE[override_key] | ||
| final_visibility ||= VISIBILITY_OVERRIDE[override_key[0..-2]] | ||
| final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name) | ||
| final_visibility ||= VISIBILITY_OVERRIDE[[closure.path, final_scope]] | ||
| final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name.to_sym) | ||
| final_visibility ||= visibility | ||
@@ -34,0 +34,0 @@ final_visibility ||= :private if code_object.module_function? && final_scope == :instance |
| # frozen_string_literal: true | ||
| require 'open3' | ||
| module Solargraph | ||
@@ -12,5 +14,6 @@ # Methods for caching and loading YARD documentation for gems. | ||
| # | ||
| # @param yard_plugins [Array<String>] The names of YARD plugins to use. | ||
| # @param gemspec [Gem::Specification] | ||
| # @return [String] The path to the cached yardoc. | ||
| def cache(gemspec) | ||
| def cache(yard_plugins, gemspec) | ||
| path = PinCache.yardoc_path gemspec | ||
@@ -20,4 +23,13 @@ return path if cached?(gemspec) | ||
| Solargraph.logger.info "Caching yardoc for #{gemspec.name} #{gemspec.version}" | ||
| Dir.chdir gemspec.gem_dir do | ||
| `yardoc --db #{path} --no-output --plugin solargraph` | ||
| cmd = "yardoc --db #{path} --no-output --plugin solargraph" | ||
| yard_plugins.each { |plugin| cmd << " --plugin #{plugin}" } | ||
| Solargraph.logger.debug { "Running: #{cmd}" } | ||
| # @todo set these up to run in parallel | ||
| # | ||
| # @sg-ignore RBS gem doesn't reflect that Open3.* also include | ||
| # kwopts from Process.spawn() | ||
| stdout_and_stderr_str, status = Open3.capture2e(cmd, chdir: gemspec.gem_dir) | ||
| unless status.success? | ||
| Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" } | ||
| Solargraph.logger.info stdout_and_stderr_str | ||
| end | ||
@@ -37,2 +49,3 @@ path | ||
| # | ||
| # @param gemspec [Gem::Specification] | ||
| def processing?(gemspec) | ||
@@ -39,0 +52,0 @@ yardoc = File.join(PinCache.yardoc_path(gemspec), 'processing') |
+125
-13
| require 'rake' | ||
| require 'rspec/core/rake_task' | ||
| require 'bundler/gem_tasks' | ||
| require 'fileutils' | ||
| require 'open3' | ||
| begin | ||
| require 'rspec/core/rake_task' | ||
| RSpec::Core::RakeTask.new(:spec) | ||
| rescue LoadError | ||
| end | ||
| desc "Open a Pry session preloaded with this library" | ||
@@ -17,10 +12,127 @@ task :console do | ||
| desc "Run the type checker" | ||
| task :typecheck do | ||
| sh "bundle exec solargraph typecheck --level typed" | ||
| task typecheck: [:typecheck_typed] | ||
| 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 all tests" | ||
| task :test do | ||
| Rake::Task["typecheck"].invoke | ||
| Rake::Task["spec"].invoke | ||
| 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}" | ||
| 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 |
@@ -48,3 +48,2 @@ # <-- liberally borrowed from | ||
| | (9 index) -> J | ||
| | (0 index) -> K | ||
| | (int index) -> nil | ||
@@ -137,3 +136,3 @@ | ||
| | [T] (9 index, T default) -> (J | T) | ||
| | [T] (int index, T default) -> (A | B | C | D | E | F |G | H | I | J | T) | ||
| | [T] (int index, T default) -> (A | B | C | D | E | F | G | H | I | J | T) | ||
| | [T] (0 index) { (int index) -> T } -> (A | T) | ||
@@ -149,5 +148,5 @@ | [T] (1 index) { (int index) -> T } -> (B | T) | ||
| | [T] (9 index) { (int index) -> T } -> (J | T) | ||
| | [T] (int index) { (int index) -> T } -> (A | B | C | D | E | F |G | H | I | J | T) | ||
| | [T] (int index) { (int index) -> T } -> (A | B | C | D | E | F | G | H | I | J | T) | ||
| end | ||
| end | ||
| end |
+8
-4
@@ -56,3 +56,3 @@ # Solargraph | ||
| Solargraph supports [plugins](https://solargraph.org/guides/plugins) that implements their own Solargraph features, such as diagnostics reporters and conventions to provide LSP features and type-checking, e.g. for frameworks which use metaprogramming and/or DSLs. | ||
| Solargraph supports [plugins](https://solargraph.org/guides/plugins) that implement their own Solargraph features, such as diagnostics reporters and conventions to provide LSP features and type-checking, e.g. for frameworks which use metaprogramming and/or DSLs. | ||
@@ -69,3 +69,3 @@ For better Rails support, please consider using [solargraph-rails](https://github.com/iftheshoefritz/solargraph-rails/) | ||
| If your project automatically requires bundled gems (e.g., `require 'bundler/require'`), Solargraph will add all of the Gemfile's default dependencies to the map. | ||
| If your project automatically requires bundled gem with the `Bundler.require` statement, Solargraph will add all of the Gemfile's default dependencies to the map. | ||
@@ -86,3 +86,3 @@ To ensure you have types for gems which contain neither RBS nor YARD | ||
| As of version 0.33.0, Solargraph includes a [type checker](https://github.com/castwide/solargraph/issues/192) that uses a combination of YARD tags and code analysis to report missing type definitions. In strict mode, it performs type inference to determine whether the tags match the types it detects from code. | ||
| As of version 0.33.0, Solargraph includes a [type checker](https://github.com/castwide/solargraph/issues/192) that uses a combination of YARD tags and code analysis to report missing type definitions. In strict mode, it performs type inference to determine whether the tags match the types it detects from code. In strong mode it will ask you to clarify your intentions by adding annotations for better validation. | ||
@@ -107,3 +107,3 @@ ### The Documentation Cache | ||
| ### Rubocop Version | ||
| ### RuboCop Version | ||
@@ -139,2 +139,6 @@ If you have multiple versions of [`rubocop`](https://rubygems.org/gems/rubocop) installed and you would like to choose a version other than the latest to use, this specific version can be configured. | ||
| To see more logging when typechecking or running specs, set the | ||
| `SOLARGRAPH_LOG` environment variable to `debug` or `info`. `warn` is | ||
| the default value. | ||
| Code contributions are always appreciated. Feel free to fork the repo and submit pull requests. Check for open issues that could use help. Start new issues to discuss changes that have a major impact on the code or require large time commitments. | ||
@@ -141,0 +145,0 @@ |
+14
-4
@@ -14,3 +14,6 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib' | ||
| s.files = Dir.chdir(File.expand_path('..', __FILE__)) do | ||
| `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } | ||
| # @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 | ||
@@ -39,5 +42,5 @@ s.homepage = 'https://solargraph.org' | ||
| s.add_runtime_dependency 'prism', '~> 1.4' | ||
| s.add_runtime_dependency 'rbs', '~> 3.6.1' | ||
| s.add_runtime_dependency 'rbs', ['>= 3.6.1', '<= 4.0.0.dev.4'] | ||
| s.add_runtime_dependency 'reverse_markdown', '~> 3.0' | ||
| s.add_runtime_dependency 'rubocop', '~> 1.38' | ||
| s.add_runtime_dependency 'rubocop', '~> 1.76' | ||
| s.add_runtime_dependency 'thor', '~> 1.0' | ||
@@ -47,2 +50,3 @@ s.add_runtime_dependency 'tilt', '~> 2.0' | ||
| s.add_runtime_dependency 'yard-solargraph', '~> 0.1' | ||
| s.add_runtime_dependency 'yard-activesupport-concern', '~> 0.0' | ||
@@ -53,3 +57,9 @@ s.add_development_dependency 'pry', '~> 0.15' | ||
| s.add_development_dependency 'rspec', '~> 3.5' | ||
| s.add_development_dependency 'simplecov', '~> 0.14' | ||
| s.add_development_dependency 'rubocop-rake', '~> 0.7' | ||
| s.add_development_dependency 'rubocop-rspec', '~> 3.6' | ||
| s.add_development_dependency 'rubocop-yard', '~> 1.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' | ||
@@ -56,0 +66,0 @@ # work around missing yard dependency needed as of Ruby 3.5 |
| AllCops: | ||
| NewCops: enable | ||
| Style/MethodDefParentheses: | ||
| Enabled: false | ||
| Layout/EmptyLineAfterGuardClause: | ||
| Enabled: false | ||
| Layout/SpaceAroundMethodCallOperator: | ||
| Enabled: true | ||
| Lint/RaiseException: | ||
| Enabled: true | ||
| Lint/StructNewOverride: | ||
| Enabled: true | ||
| Metrics/MethodLength: | ||
| Max: 25 | ||
| Style/ExponentialNotation: | ||
| Enabled: true | ||
| Style/HashEachMethods: | ||
| Enabled: true | ||
| Style/HashTransformKeys: | ||
| Enabled: true | ||
| Style/HashTransformValues: | ||
| Enabled: true |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet