holistic-ruby 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +5 -5
- data/exe/holistic-ruby +0 -1
- data/lib/holistic/application.rb +1 -1
- data/lib/holistic/database/migrations.rb +9 -6
- data/lib/holistic/database/node.rb +33 -25
- data/lib/holistic/database/relation.rb +21 -0
- data/lib/holistic/database.rb +57 -0
- data/lib/holistic/document/unsaved/record.rb +13 -0
- data/lib/holistic/extensions/ruby/stdlib.rb +3 -3
- data/lib/holistic/language_server/requests/text_document/completion.rb +13 -3
- data/lib/holistic/language_server/stdio/parser.rb +2 -2
- data/lib/holistic/language_server/stdio/start.rb +1 -1
- data/lib/holistic/ruby/autocompletion/suggest.rb +22 -22
- data/lib/holistic/ruby/parser/constant_resolution.rb +3 -3
- data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +3 -3
- data/lib/holistic/ruby/parser/program_visitor.rb +41 -5
- data/lib/holistic/ruby/parser.rb +45 -2
- data/lib/holistic/ruby/reference/delete.rb +3 -3
- data/lib/holistic/ruby/reference/record.rb +8 -0
- data/lib/holistic/ruby/reference/repository.rb +1 -1
- data/lib/holistic/ruby/reference/store.rb +14 -3
- data/lib/holistic/ruby/scope/delete.rb +2 -2
- data/lib/holistic/ruby/scope/lexical.rb +1 -1
- data/lib/holistic/ruby/scope/list_class_methods.rb +19 -0
- data/lib/holistic/ruby/scope/list_instance_methods.rb +19 -0
- data/lib/holistic/ruby/scope/list_references.rb +2 -2
- data/lib/holistic/ruby/scope/outline.rb +2 -2
- data/lib/holistic/ruby/scope/record.rb +9 -3
- data/lib/holistic/ruby/scope/repository.rb +7 -3
- data/lib/holistic/ruby/scope/store.rb +12 -12
- data/lib/holistic/ruby/type_inference/clue/method_call.rb +1 -0
- data/lib/holistic/ruby/type_inference/clue/reference_to_superclass.rb +9 -0
- data/lib/holistic/ruby/type_inference/clue/scope_reference.rb +1 -0
- data/lib/holistic/ruby/type_inference/processing_queue.rb +21 -12
- data/lib/holistic/ruby/type_inference/resolver/class_method.rb +9 -0
- data/lib/holistic/ruby/type_inference/resolver/instance_method.rb +9 -0
- data/lib/holistic/ruby/type_inference/resolver/scope.rb +24 -0
- data/lib/holistic/ruby/type_inference/solve.rb +18 -53
- data/lib/holistic/version.rb +1 -1
- metadata +10 -3
- data/lib/holistic/database/table.rb +0 -78
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b5eda7bf8e9da75eb3716452dfc2ebafdbaf35af0a53aef452c64bd95cd00bd8
         | 
| 4 | 
            +
              data.tar.gz: 5522698edac5824011fd5aeee3ba4c71eb5cfee51b956c3d5942e329c7644072
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 06caa82194be9d2d2d9832aefd9132ab3d3cc1838be8b2cff9cd64f3fbafa36a9f0f79c40384eb4439adfd224a559605a9e328ae7dfb722235870b535f0d155f
         | 
| 7 | 
            +
              data.tar.gz: a542c510078f1f528b4621000ba87843d0544e23dea77123bcf627e79d17f8dcdcb987be4122bd0f39c7f96533fc57d1a26e489e0cc25b6f5e0e5dfc68c436d3
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -5,7 +5,7 @@ | |
| 5 5 | 
             
            ## Installation for Sublime Text
         | 
| 6 6 |  | 
| 7 7 | 
             
            1. Make sure you have the [LSP package installed](https://github.com/sublimelsp/LSP).
         | 
| 8 | 
            -
            2. Install the gem `$ gem install holistic-ruby`
         | 
| 8 | 
            +
            2. Install the gem with `$ gem install holistic-ruby`
         | 
| 9 9 | 
             
            3. Go to `Preferences > Package Settings > LSP > Settings` and add:
         | 
| 10 10 |  | 
| 11 11 | 
             
            ```json
         | 
| @@ -25,10 +25,10 @@ | |
| 25 25 |  | 
| 26 26 | 
             
            * Go to definition.
         | 
| 27 27 | 
             
            * Find references.
         | 
| 28 | 
            -
            * Autocompletion for namespaces | 
| 29 | 
            -
            *  | 
| 30 | 
            -
            *  | 
| 31 | 
            -
            * Glossary.
         | 
| 28 | 
            +
            * Autocompletion for namespaces and methods.
         | 
| 29 | 
            +
            * (WIP) Outline dependencies.
         | 
| 30 | 
            +
            * (WIP) Syntax highlighting boundaries based on packwerk.
         | 
| 31 | 
            +
            * (WIP) Glossary.
         | 
| 32 32 |  | 
| 33 33 | 
             
            ## Why is it a toy language server?
         | 
| 34 34 |  | 
    
        data/exe/holistic-ruby
    CHANGED
    
    
    
        data/lib/holistic/application.rb
    CHANGED
    
    
| @@ -2,19 +2,22 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            module Holistic::Database::Migrations
         | 
| 4 4 | 
             
              Run = ->(database) do
         | 
| 5 | 
            -
                # scope parent-children relation
         | 
| 6 | 
            -
                database. | 
| 5 | 
            +
                # scope lexical parent-children relation
         | 
| 6 | 
            +
                database.define_relation(name: :lexical_children, inverse_of: :lexical_parent)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                # scope inheritance and mixins
         | 
| 9 | 
            +
                database.define_relation(name: :ancestors, inverse_of: :descendants)
         | 
| 7 10 |  | 
| 8 11 | 
             
                # type inference conclusion
         | 
| 9 | 
            -
                database. | 
| 12 | 
            +
                database.define_relation(name: :referenced_scope, inverse_of: :referenced_by)
         | 
| 10 13 |  | 
| 11 14 | 
             
                # reference definition
         | 
| 12 | 
            -
                database. | 
| 15 | 
            +
                database.define_relation(name: :located_in_scope, inverse_of: :contains_many_references)
         | 
| 13 16 |  | 
| 14 17 | 
             
                # scope location in files
         | 
| 15 | 
            -
                database. | 
| 18 | 
            +
                database.define_relation(name: :defines_scopes, inverse_of: :scope_defined_in_file)
         | 
| 16 19 |  | 
| 17 20 | 
             
                # reference location in files
         | 
| 18 | 
            -
                database. | 
| 21 | 
            +
                database.define_relation(name: :defines_references, inverse_of: :reference_defined_in_file)
         | 
| 19 22 | 
             
              end
         | 
| 20 23 | 
             
            end
         | 
| @@ -1,29 +1,37 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
               | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
                 | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
                 | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
                 | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
                 | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
                 | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 3 | 
            +
            class Holistic::Database::Node
         | 
| 4 | 
            +
              attr_accessor :attributes, :relations, :__database__
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize(id, attributes)
         | 
| 7 | 
            +
                @id = id
         | 
| 8 | 
            +
                @attributes = attributes
         | 
| 9 | 
            +
                @relations = ::Hash.new(&method(:build_relation_hash))
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def attr(attribute_name)
         | 
| 13 | 
            +
                @attributes[attribute_name]
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def relation(relation_name)
         | 
| 17 | 
            +
                @relations[relation_name]
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def has_many(connection_name)
         | 
| 21 | 
            +
                @relations[connection_name].to_a
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def has_one(connection_name)
         | 
| 25 | 
            +
                @relations[connection_name].first
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def build_relation_hash(hash, name)
         | 
| 31 | 
            +
                inverse_of = __database__.relations.dig(name, :inverse_of)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                raise ::ArgumentError, "unknown relation: #{name}" if inverse_of.nil?
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                hash[name] = ::Holistic::Database::Relation.new(node: self, name:, inverse_of:)
         | 
| 28 36 | 
             
              end
         | 
| 29 37 | 
             
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Holistic::Database::Relation < ::Set
         | 
| 4 | 
            +
              def initialize(node:, name:, inverse_of:)
         | 
| 5 | 
            +
                @node = node
         | 
| 6 | 
            +
                @name = name
         | 
| 7 | 
            +
                @inverse_of = inverse_of
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                super()
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def add!(related_node)
         | 
| 13 | 
            +
                @node.relations[@name].add(related_node)
         | 
| 14 | 
            +
                related_node.relations[@inverse_of].add(@node)
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def delete!(related_node)
         | 
| 18 | 
            +
                @node.relations[@name].delete(related_node)
         | 
| 19 | 
            +
                related_node.relations[@inverse_of].delete(@node)
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Holistic::Database
         | 
| 4 | 
            +
              attr_reader :records, :relations
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize
         | 
| 7 | 
            +
                @records = ::Hash.new
         | 
| 8 | 
            +
                @relations = ::Hash.new
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def define_relation(name:, inverse_of:)
         | 
| 12 | 
            +
                raise ::ArgumentError if @relations.key?(name) || @relations.key?(inverse_of)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                @relations[name] = { inverse_of: }
         | 
| 15 | 
            +
                @relations[inverse_of] = { inverse_of: name }
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def store(id, node_or_attrs)
         | 
| 19 | 
            +
                if @records.key?(id)
         | 
| 20 | 
            +
                  return @records[id]&.tap do |node|
         | 
| 21 | 
            +
                    node.attributes =
         | 
| 22 | 
            +
                      case node_or_attrs
         | 
| 23 | 
            +
                      in ::Hash then node_or_attrs
         | 
| 24 | 
            +
                      in Node   then node_or_attrs.attributes
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                node =
         | 
| 30 | 
            +
                  case node_or_attrs
         | 
| 31 | 
            +
                  in ::Hash then Node.new(id, node_or_attrs)
         | 
| 32 | 
            +
                  in Node   then node_or_attrs
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                node.__database__ = self
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                @records[id] = node
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def find(id)
         | 
| 41 | 
            +
                @records[id]
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              def delete(id)
         | 
| 45 | 
            +
                records.delete(id)
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              concerning :TestHelpers do
         | 
| 49 | 
            +
                def all
         | 
| 50 | 
            +
                  records.values
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def size
         | 
| 54 | 
            +
                  records.size
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -54,6 +54,14 @@ module Holistic::Document | |
| 54 54 | 
             
                  line = 0
         | 
| 55 55 | 
             
                  column = 0
         | 
| 56 56 |  | 
| 57 | 
            +
                  # first edition to the document is special because we can't iterate over the content to find the insert position.
         | 
| 58 | 
            +
                  # there is nothing to iterate over.
         | 
| 59 | 
            +
                  if @content.empty? && change.insertion?
         | 
| 60 | 
            +
                    @content = change.text
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    return
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 57 65 | 
             
                  @content.each_char.with_index do |char, index|
         | 
| 58 66 | 
             
                    if change.insertion? && change.starts_on?(line, column)
         | 
| 59 67 | 
             
                      content.insert(index, change.text)
         | 
| @@ -74,6 +82,11 @@ module Holistic::Document | |
| 74 82 | 
             
                      column += 1
         | 
| 75 83 | 
             
                    end
         | 
| 76 84 | 
             
                  end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  # off-by-one error to insert at the of the document
         | 
| 87 | 
            +
                  if change.insertion? && change.starts_on?(line, column)
         | 
| 88 | 
            +
                    content.insert(@content.length, change.text)
         | 
| 89 | 
            +
                  end
         | 
| 77 90 | 
             
                end
         | 
| 78 91 | 
             
              end
         | 
| 79 92 | 
             
            end
         | 
| @@ -19,12 +19,12 @@ module Holistic::Extensions::Ruby | |
| 19 19 | 
             
                RegisterClassConstructor = ->(application, params) do
         | 
| 20 20 | 
             
                  class_scope, location = params[:class_scope], params[:location]
         | 
| 21 21 |  | 
| 22 | 
            -
                  has_overridden_new_method = class_scope. | 
| 22 | 
            +
                  has_overridden_new_method = class_scope.lexical_children.find { _1.instance_method? && _1.name == "initialize" }
         | 
| 23 23 |  | 
| 24 24 | 
             
                  unless has_overridden_new_method
         | 
| 25 25 | 
             
                    ::Holistic::Ruby::Scope::Store.call(
         | 
| 26 26 | 
             
                      database: application.database,
         | 
| 27 | 
            -
                       | 
| 27 | 
            +
                      lexical_parent: class_scope,
         | 
| 28 28 | 
             
                      kind: ::Holistic::Ruby::Scope::Kind::CLASS_METHOD,
         | 
| 29 29 | 
             
                      name: "new",
         | 
| 30 30 | 
             
                      location:
         | 
| @@ -40,7 +40,7 @@ module Holistic::Extensions::Ruby | |
| 40 40 | 
             
                  LAMBDA_METHODS.each do |method_name|
         | 
| 41 41 | 
             
                    ::Holistic::Ruby::Scope::Store.call(
         | 
| 42 42 | 
             
                      database: application.database,
         | 
| 43 | 
            -
                       | 
| 43 | 
            +
                      lexical_parent: lambda_scope,
         | 
| 44 44 | 
             
                      kind: ::Holistic::Ruby::Scope::Kind::CLASS_METHOD,
         | 
| 45 45 | 
             
                      name: method_name,
         | 
| 46 46 | 
             
                      location:
         | 
| @@ -8,8 +8,12 @@ module Holistic::LanguageServer | |
| 8 8 | 
             
                  cursor = build_cursor_from_request_params(request)
         | 
| 9 9 |  | 
| 10 10 | 
             
                  document = request.application.unsaved_documents.find(cursor.file_path)
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                   | 
| 11 | 
            +
             | 
| 12 | 
            +
                  if document.nil?
         | 
| 13 | 
            +
                    ::Holistic.logger.info("aborting completion because document was not found for #{cursor.file_path}")
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    return request.respond_with(nil) 
         | 
| 16 | 
            +
                  end
         | 
| 13 17 |  | 
| 14 18 | 
             
                  if document.has_unsaved_changes?
         | 
| 15 19 | 
             
                    ::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
         | 
| @@ -21,10 +25,16 @@ module Holistic::LanguageServer | |
| 21 25 |  | 
| 22 26 | 
             
                  code = document.expand_code(cursor)
         | 
| 23 27 |  | 
| 24 | 
            -
                   | 
| 28 | 
            +
                  if code.blank?
         | 
| 29 | 
            +
                    ::Holistic.logger.info("aborting completion because code under cursor was blank: #{cursor.inspect}")
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    return request.respond_with(nil)
         | 
| 32 | 
            +
                  end
         | 
| 25 33 |  | 
| 26 34 | 
             
                  scope = request.application.scopes.find_inner_most_scope_by_cursor(cursor) || request.application.scopes.root
         | 
| 27 35 |  | 
| 36 | 
            +
                  ::Holistic.logger.info("scope under cursor is: #{scope.fully_qualified_name}")
         | 
| 37 | 
            +
             | 
| 28 38 | 
             
                  suggestions = ::Holistic::Ruby::Autocompletion::Suggest.call(code:, scope:)
         | 
| 29 39 |  | 
| 30 40 | 
             
                  respond_with_suggestions(request, suggestions)
         | 
| @@ -11,7 +11,7 @@ module Holistic::LanguageServer | |
| 11 11 |  | 
| 12 12 | 
             
                def ingest(payload)
         | 
| 13 13 | 
             
                  payload.each_char do |char|
         | 
| 14 | 
            -
                    if @in_header || ! | 
| 14 | 
            +
                    if @in_header || !has_complete_message?
         | 
| 15 15 | 
             
                      @buffer.concat(char)
         | 
| 16 16 | 
             
                    else
         | 
| 17 17 | 
             
                      @overflow_from_previous_ingestion.concat(char)
         | 
| @@ -23,7 +23,7 @@ module Holistic::LanguageServer | |
| 23 23 | 
             
                  end
         | 
| 24 24 | 
             
                end
         | 
| 25 25 |  | 
| 26 | 
            -
                def  | 
| 26 | 
            +
                def has_complete_message?
         | 
| 27 27 | 
             
                  !@in_header && @content_length == @buffer.length
         | 
| 28 28 | 
             
                end
         | 
| 29 29 |  | 
| @@ -16,7 +16,7 @@ module Holistic::Ruby::Autocompletion | |
| 16 16 | 
             
                  lookup_scope = scope
         | 
| 17 17 |  | 
| 18 18 | 
             
                  if code.start_with?("::")
         | 
| 19 | 
            -
                    lookup_scope = lookup_scope. | 
| 19 | 
            +
                    lookup_scope = lookup_scope.lexical_parent until lookup_scope.root?
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 |  | 
| 22 22 | 
             
                  if StartsWithLowerCaseLetter[code]
         | 
| @@ -33,23 +33,23 @@ module Holistic::Ruby::Autocompletion | |
| 33 33 | 
             
                def suggest_local_methods_from_current_scope(code:, scope:)
         | 
| 34 34 | 
             
                  suggestions = []
         | 
| 35 35 |  | 
| 36 | 
            -
                   | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 36 | 
            +
                  sibling_methods =
         | 
| 37 | 
            +
                    case scope.kind
         | 
| 38 | 
            +
                    when ::Holistic::Ruby::Scope::Kind::CLASS_METHOD
         | 
| 39 | 
            +
                      ::Holistic::Ruby::Scope::ListClassMethods.call(scope: scope.lexical_parent)
         | 
| 40 | 
            +
                    when ::Holistic::Ruby::Scope::Kind::INSTANCE_METHOD
         | 
| 41 | 
            +
                      ::Holistic::Ruby::Scope::ListInstanceMethods.call(scope: scope.lexical_parent)
         | 
| 42 | 
            +
                    when ::Holistic::Ruby::Scope::Kind::ROOT
         | 
| 43 | 
            +
                      # TODO: global functions?
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      return []
         | 
| 46 | 
            +
                    else
         | 
| 47 | 
            +
                      raise "unexpected scope kind: #{scope.kind}"
         | 
| 45 48 | 
             
                    end
         | 
| 46 | 
            -
                  elsif scope.class_method?
         | 
| 47 | 
            -
                    sibling_methods = scope.parent.children.filter { _1.class_method? }
         | 
| 48 49 |  | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
                      end
         | 
| 50 | 
            +
                  sibling_methods.each do |method_scope|
         | 
| 51 | 
            +
                    if method_scope.name.start_with?(code)
         | 
| 52 | 
            +
                      suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
         | 
| 53 53 | 
             
                    end
         | 
| 54 54 | 
             
                  end
         | 
| 55 55 |  | 
| @@ -69,7 +69,7 @@ module Holistic::Ruby::Autocompletion | |
| 69 69 | 
             
                    return suggestions if scope.nil?
         | 
| 70 70 | 
             
                  end
         | 
| 71 71 |  | 
| 72 | 
            -
                  class_methods = scope | 
| 72 | 
            +
                  class_methods = ::Holistic::Ruby::Scope::ListClassMethods.call(scope:)
         | 
| 73 73 |  | 
| 74 74 | 
             
                  class_methods.each do |method_scope|
         | 
| 75 75 | 
             
                    if method_scope.name.start_with?(method_to_autocomplete)
         | 
| @@ -96,7 +96,7 @@ module Holistic::Ruby::Autocompletion | |
| 96 96 | 
             
                  should_search_upwards = namespaces_to_resolve.empty?
         | 
| 97 97 |  | 
| 98 98 | 
             
                  search = ->(scope) do
         | 
| 99 | 
            -
                    scope. | 
| 99 | 
            +
                    scope.lexical_children.each do |child_scope|
         | 
| 100 100 | 
             
                      next if child_scope.method?
         | 
| 101 101 |  | 
| 102 102 | 
             
                      if child_scope.name.start_with?(namespace_to_autocomplete)
         | 
| @@ -104,7 +104,7 @@ module Holistic::Ruby::Autocompletion | |
| 104 104 | 
             
                      end
         | 
| 105 105 | 
             
                    end
         | 
| 106 106 |  | 
| 107 | 
            -
                    search.(scope. | 
| 107 | 
            +
                    search.(scope.lexical_parent) if scope.lexical_parent.present? && should_search_upwards
         | 
| 108 108 | 
             
                  end
         | 
| 109 109 |  | 
| 110 110 | 
             
                  search.(scope)
         | 
| @@ -113,10 +113,10 @@ module Holistic::Ruby::Autocompletion | |
| 113 113 | 
             
                end
         | 
| 114 114 |  | 
| 115 115 | 
             
                def resolve_scope(name:, from_scope:)
         | 
| 116 | 
            -
                  resolved_scope = from_scope. | 
| 116 | 
            +
                  resolved_scope = from_scope.lexical_children.find { |scope| scope.name == name }
         | 
| 117 117 |  | 
| 118 | 
            -
                  if resolved_scope.nil? && from_scope. | 
| 119 | 
            -
                    resolved_scope = resolve_scope(name:, from_scope: from_scope. | 
| 118 | 
            +
                  if resolved_scope.nil? && from_scope.lexical_parent.present?
         | 
| 119 | 
            +
                    resolved_scope = resolve_scope(name:, from_scope: from_scope.lexical_parent)
         | 
| 120 120 | 
             
                  end
         | 
| 121 121 |  | 
| 122 122 | 
             
                  resolved_scope
         | 
| @@ -27,7 +27,7 @@ module Holistic::Ruby::Parser | |
| 27 27 | 
             
                    @scope =
         | 
| 28 28 | 
             
                      ::Holistic::Ruby::Scope::Store.call(
         | 
| 29 29 | 
             
                        database: @scope_repository.database,
         | 
| 30 | 
            -
                         | 
| 30 | 
            +
                        lexical_parent: @scope,
         | 
| 31 31 | 
             
                        kind: ::Holistic::Ruby::Scope::Kind::MODULE,
         | 
| 32 32 | 
             
                        name:,
         | 
| 33 33 | 
             
                        location:
         | 
| @@ -54,7 +54,7 @@ module Holistic::Ruby::Parser | |
| 54 54 | 
             
                    @scope =
         | 
| 55 55 | 
             
                      ::Holistic::Ruby::Scope::Store.call(
         | 
| 56 56 | 
             
                        database: @scope_repository.database,
         | 
| 57 | 
            -
                         | 
| 57 | 
            +
                        lexical_parent: @scope,
         | 
| 58 58 | 
             
                        kind: ::Holistic::Ruby::Scope::Kind::CLASS,
         | 
| 59 59 | 
             
                        name:,
         | 
| 60 60 | 
             
                        location:
         | 
| @@ -81,7 +81,7 @@ module Holistic::Ruby::Parser | |
| 81 81 | 
             
                    @scope =
         | 
| 82 82 | 
             
                      ::Holistic::Ruby::Scope::Store.call(
         | 
| 83 83 | 
             
                        database: @scope_repository.database,
         | 
| 84 | 
            -
                         | 
| 84 | 
            +
                        lexical_parent: @scope,
         | 
| 85 85 | 
             
                        kind:,
         | 
| 86 86 | 
             
                        name:,
         | 
| 87 87 | 
             
                        location:
         | 
| @@ -15,7 +15,7 @@ module Holistic::Ruby::Parser | |
| 15 15 |  | 
| 16 16 | 
             
                  parse_again(application:, file_path:, content:)
         | 
| 17 17 |  | 
| 18 | 
            -
                   | 
| 18 | 
            +
                  recalculate_type_inference(application:, references: references_to_recalculate)
         | 
| 19 19 | 
             
                end
         | 
| 20 20 |  | 
| 21 21 | 
             
                private
         | 
| @@ -54,9 +54,9 @@ module Holistic::Ruby::Parser | |
| 54 54 | 
             
                  ::Holistic::Ruby::TypeInference::SolvePendingReferences.call(application:)
         | 
| 55 55 | 
             
                end
         | 
| 56 56 |  | 
| 57 | 
            -
                def  | 
| 57 | 
            +
                def recalculate_type_inference(application:, references:)
         | 
| 58 58 | 
             
                  references.each do |reference|
         | 
| 59 | 
            -
                     | 
| 59 | 
            +
                    reference.relation(:referenced_scope).delete!(reference.referenced_scope)
         | 
| 60 60 |  | 
| 61 61 | 
             
                    ::Holistic::Ruby::TypeInference::Solve.call(application:, reference:)
         | 
| 62 62 | 
             
                  end
         | 
| @@ -27,10 +27,6 @@ module Holistic::Ruby::Parser | |
| 27 27 | 
             
                  def visit_class(node)
         | 
| 28 28 | 
             
                    declaration_node, superclass_node, body_statements_node = node.child_nodes
         | 
| 29 29 |  | 
| 30 | 
            -
                    if superclass_node
         | 
| 31 | 
            -
                      register_reference(nesting: NestingSyntax.build(superclass_node), location: build_location(superclass_node))
         | 
| 32 | 
            -
                    end
         | 
| 33 | 
            -
             | 
| 34 30 | 
             
                    nesting = NestingSyntax.build(declaration_node)
         | 
| 35 31 | 
             
                    location = build_scope_location(declaration_node:, body_node: node)
         | 
| 36 32 |  | 
| @@ -38,6 +34,25 @@ module Holistic::Ruby::Parser | |
| 38 34 | 
             
                      visit(body_statements_node)
         | 
| 39 35 | 
             
                    end
         | 
| 40 36 |  | 
| 37 | 
            +
                    if superclass_node
         | 
| 38 | 
            +
                      reference_to_scope_clue = ::Holistic::Ruby::TypeInference::Clue::ScopeReference.new(
         | 
| 39 | 
            +
                        nesting: NestingSyntax.build(superclass_node),
         | 
| 40 | 
            +
                        resolution_possibilities: @constant_resolution.current
         | 
| 41 | 
            +
                      )
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                      reference_to_superclass_clue = ::Holistic::Ruby::TypeInference::Clue::ReferenceToSuperclass.new(
         | 
| 44 | 
            +
                        subclass_scope: class_scope
         | 
| 45 | 
            +
                      )
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                      ::Holistic::Ruby::Reference::Store.call(
         | 
| 48 | 
            +
                        database: @application.database,
         | 
| 49 | 
            +
                        processing_queue: @application.type_inference_processing_queue,
         | 
| 50 | 
            +
                        scope: @constant_resolution.scope,
         | 
| 51 | 
            +
                        clues: [reference_to_scope_clue, reference_to_superclass_clue],
         | 
| 52 | 
            +
                        location: build_location(superclass_node)
         | 
| 53 | 
            +
                      )
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
             | 
| 41 56 | 
             
                    @application.extensions.dispatch(:class_scope_registered, { class_scope:, location: })
         | 
| 42 57 | 
             
                  end
         | 
| 43 58 |  | 
| @@ -50,6 +65,27 @@ module Holistic::Ruby::Parser | |
| 50 65 | 
             
                      @constant_resolution.change_method_registration_mode_to_class_methods! if is_extending_self
         | 
| 51 66 | 
             
                    end
         | 
| 52 67 |  | 
| 68 | 
            +
                    if command_name_node.value == "include"
         | 
| 69 | 
            +
                      superclass_nesting_syntax = NestingSyntax.build(args_node.child_nodes.first)
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                      reference_to_scope_clue = ::Holistic::Ruby::TypeInference::Clue::ScopeReference.new(
         | 
| 72 | 
            +
                        nesting: superclass_nesting_syntax,
         | 
| 73 | 
            +
                        resolution_possibilities: @constant_resolution.current
         | 
| 74 | 
            +
                      )
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                      reference_to_superclass_clue = ::Holistic::Ruby::TypeInference::Clue::ReferenceToSuperclass.new(
         | 
| 77 | 
            +
                        subclass_scope: @constant_resolution.scope
         | 
| 78 | 
            +
                      )
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                      ::Holistic::Ruby::Reference::Store.call(
         | 
| 81 | 
            +
                        database: @application.database,
         | 
| 82 | 
            +
                        processing_queue: @application.type_inference_processing_queue,
         | 
| 83 | 
            +
                        scope: @constant_resolution.scope,
         | 
| 84 | 
            +
                        clues: [reference_to_scope_clue, reference_to_superclass_clue],
         | 
| 85 | 
            +
                        location: build_location(node)
         | 
| 86 | 
            +
                      )
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 53 89 | 
             
                    visit(args_node)
         | 
| 54 90 | 
             
                  end
         | 
| 55 91 |  | 
| @@ -155,7 +191,7 @@ module Holistic::Ruby::Parser | |
| 155 191 | 
             
                    lambda_scope =
         | 
| 156 192 | 
             
                      ::Holistic::Ruby::Scope::Store.call(
         | 
| 157 193 | 
             
                        database: @application.database,
         | 
| 158 | 
            -
                         | 
| 194 | 
            +
                        lexical_parent: @constant_resolution.scope,
         | 
| 159 195 | 
             
                        kind: ::Holistic::Ruby::Scope::Kind::LAMBDA,
         | 
| 160 196 | 
             
                        name: assign_node.child_nodes.first.value,
         | 
| 161 197 | 
             
                        location:
         | 
    
        data/lib/holistic/ruby/parser.rb
    CHANGED
    
    | @@ -20,12 +20,55 @@ module Holistic::Ruby::Parser | |
| 20 20 |  | 
| 21 21 | 
             
                visitor.visit(program)
         | 
| 22 22 | 
             
              rescue ::SyntaxTree::Parser::ParseError
         | 
| 23 | 
            -
                ::Holistic.logger.info("syntax error on file #{ | 
| 23 | 
            +
                ::Holistic.logger.info("syntax error on file #{file_path}")
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              class PerformanceMetrics
         | 
| 27 | 
            +
                def initialize
         | 
| 28 | 
            +
                  @amount_of_files  = 0
         | 
| 29 | 
            +
                  @total_read_time  = 0
         | 
| 30 | 
            +
                  @total_parse_time = 0
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def start_file_read!
         | 
| 34 | 
            +
                  @amount_of_files += 1
         | 
| 35 | 
            +
                  @file_read_before = ::Process.clock_gettime(Process::CLOCK_MONOTONIC)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def end_file_read!
         | 
| 39 | 
            +
                  @total_read_time += ::Process.clock_gettime(Process::CLOCK_MONOTONIC) - @file_read_before
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def start_parse!
         | 
| 43 | 
            +
                  @parse_before = ::Process.clock_gettime(Process::CLOCK_MONOTONIC)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def end_parse!
         | 
| 47 | 
            +
                  @total_parse_time += ::Process.clock_gettime(Process::CLOCK_MONOTONIC) - @parse_before
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def inspect
         | 
| 51 | 
            +
                  {
         | 
| 52 | 
            +
                    amount_of_files:  @amount_of_files,
         | 
| 53 | 
            +
                    total_read_time:  @total_read_time,
         | 
| 54 | 
            +
                    total_parse_time: @total_parse_time
         | 
| 55 | 
            +
                  }.inspect
         | 
| 56 | 
            +
                end
         | 
| 24 57 | 
             
              end
         | 
| 25 58 |  | 
| 26 59 | 
             
              ParseDirectory = ->(application:, directory_path:) do
         | 
| 60 | 
            +
                performance_metrics = PerformanceMetrics.new
         | 
| 61 | 
            +
             | 
| 27 62 | 
             
                ::Dir.glob("#{directory_path}/**/*.rb").map do |file_path|
         | 
| 28 | 
            -
                   | 
| 63 | 
            +
                  performance_metrics.start_file_read!
         | 
| 64 | 
            +
                  content = ::File.read(file_path)
         | 
| 65 | 
            +
                  performance_metrics.end_file_read!
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  performance_metrics.start_parse!
         | 
| 68 | 
            +
                  ParseFile.call(application:, file_path:, content:)
         | 
| 69 | 
            +
                  performance_metrics.end_parse!
         | 
| 29 70 | 
             
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                performance_metrics
         | 
| 30 73 | 
             
              end
         | 
| 31 74 | 
             
            end
         | 
| @@ -7,11 +7,11 @@ module Holistic::Ruby::Reference | |
| 7 7 | 
             
                def call(database:, reference:)
         | 
| 8 8 | 
             
                  database.delete(reference.identifier)
         | 
| 9 9 |  | 
| 10 | 
            -
                   | 
| 11 | 
            -
                   | 
| 10 | 
            +
                  reference.relation(:reference_defined_in_file).delete!(reference.location.file)
         | 
| 11 | 
            +
                  reference.relation(:located_in_scope).delete!(reference.located_in_scope)
         | 
| 12 12 |  | 
| 13 13 | 
             
                  if reference.referenced_scope
         | 
| 14 | 
            -
                     | 
| 14 | 
            +
                    reference.relation(:referenced_scope).delete!(reference.referenced_scope)
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 | 
             
                end
         | 
| 17 17 | 
             
              end
         | 
| @@ -8,5 +8,13 @@ module Holistic::Ruby::Reference | |
| 8 8 |  | 
| 9 9 | 
             
                def referenced_scope = has_one(:referenced_scope)
         | 
| 10 10 | 
             
                def located_in_scope = has_one(:located_in_scope)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def find_clue(clue_kind)
         | 
| 13 | 
            +
                  clues.find { |clue| clue.is_a?(clue_kind) }
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def inspect
         | 
| 17 | 
            +
                  "<#{self.class.name} clues=[#{clues}] referenced_scope=#{referenced_scope&.fully_qualified_name}>"
         | 
| 18 | 
            +
                end
         | 
| 11 19 | 
             
              end
         | 
| 12 20 | 
             
            end
         | 
| @@ -9,10 +9,21 @@ module Holistic::Ruby::Reference | |
| 9 9 |  | 
| 10 10 | 
             
                  reference = database.store(location.identifier, record)
         | 
| 11 11 |  | 
| 12 | 
            -
                   | 
| 13 | 
            -
                   | 
| 12 | 
            +
                  reference.relation(:located_in_scope).add!(scope)
         | 
| 13 | 
            +
                  reference.relation(:reference_defined_in_file).add!(location.file)
         | 
| 14 14 |  | 
| 15 | 
            -
                   | 
| 15 | 
            +
                  # resolving reference to superclasses need to happen before resolving reference to methods because the
         | 
| 16 | 
            +
                  # relation ancestor-descentand needs to exist beforehand.
         | 
| 17 | 
            +
                  # in other words, if we try to resolve a reference to a method *before* resolving the superclass
         | 
| 18 | 
            +
                  # we might get a miss.
         | 
| 19 | 
            +
                  should_resolve_type_inference_with_high_priority =
         | 
| 20 | 
            +
                    reference.find_clue(::Holistic::Ruby::TypeInference::Clue::ReferenceToSuperclass).present?
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  if should_resolve_type_inference_with_high_priority
         | 
| 23 | 
            +
                    processing_queue.push_with_high_priority(reference)
         | 
| 24 | 
            +
                  else
         | 
| 25 | 
            +
                    processing_queue.push(reference)
         | 
| 26 | 
            +
                  end
         | 
| 16 27 | 
             
                end
         | 
| 17 28 | 
             
              end
         | 
| 18 29 | 
             
            end
         | 
| @@ -15,10 +15,10 @@ module Holistic::Ruby::Scope | |
| 15 15 |  | 
| 16 16 | 
             
                  scope.locations.delete(location_to_remove)
         | 
| 17 17 |  | 
| 18 | 
            -
                   | 
| 18 | 
            +
                  scope.relation(:scope_defined_in_file).delete!(location_to_remove.declaration.file)
         | 
| 19 19 |  | 
| 20 20 | 
             
                  if scope.locations.empty?
         | 
| 21 | 
            -
                     | 
| 21 | 
            +
                    scope.relation(:lexical_parent).delete!(scope.lexical_parent)
         | 
| 22 22 |  | 
| 23 23 | 
             
                    database.delete(fully_qualified_name)
         | 
| 24 24 | 
             
                  end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Holistic::Ruby::Scope::ListClassMethods
         | 
| 4 | 
            +
              extend self
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def call(scope:)
         | 
| 7 | 
            +
                class_methods = scope.lexical_children.filter(&:class_method?)
         | 
| 8 | 
            +
                class_method_names = ::Set.new(class_methods.map(&:name))
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                ancestor_methods = scope.ancestors.flat_map do |ancestor|
         | 
| 11 | 
            +
                  ancestor_methods = call(scope: ancestor)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # reject parent methods that were overriden by the subclass
         | 
| 14 | 
            +
                  ancestor_methods.reject { |method| class_method_names.include?(method.name) }
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                class_methods + ancestor_methods
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Holistic::Ruby::Scope::ListInstanceMethods
         | 
| 4 | 
            +
              extend self
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def call(scope:)
         | 
| 7 | 
            +
                instance_methods = scope.lexical_children.filter(&:instance_method?)
         | 
| 8 | 
            +
                instance_method_names = ::Set.new(instance_methods.map(&:name))
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                ancestor_methods = scope.ancestors.flat_map do |ancestor|
         | 
| 11 | 
            +
                  ancestor_methods = call(scope: ancestor)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # reject parent methods that were overriden by the subclass
         | 
| 14 | 
            +
                  ancestor_methods.reject { |method| instance_method_names.include?(method.name) }
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                instance_methods + ancestor_methods
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -5,9 +5,9 @@ module Holistic::Ruby::Scope | |
| 5 5 | 
             
                extend self
         | 
| 6 6 |  | 
| 7 7 | 
             
                QueryReferencesRecursively = ->(application, scope) do
         | 
| 8 | 
            -
                  references_to_scope = scope.referenced_by
         | 
| 8 | 
            +
                  references_to_scope = scope.referenced_by.to_a
         | 
| 9 9 |  | 
| 10 | 
            -
                  references_to_child_scopes = scope. | 
| 10 | 
            +
                  references_to_child_scopes = scope.lexical_children.flat_map { QueryReferencesRecursively.call(application, _1) }
         | 
| 11 11 |  | 
| 12 12 | 
             
                  references_to_scope + references_to_child_scopes
         | 
| 13 13 | 
             
                end
         | 
| @@ -13,7 +13,7 @@ module Holistic::Ruby::Scope | |
| 13 13 | 
             
                )
         | 
| 14 14 |  | 
| 15 15 | 
             
                QueryChildScopesRecursively = ->(application, scope) do
         | 
| 16 | 
            -
                  scope. | 
| 16 | 
            +
                  scope.lexical_children.to_a + scope.lexical_children.flat_map { QueryChildScopesRecursively[application, _1] }
         | 
| 17 17 | 
             
                end
         | 
| 18 18 |  | 
| 19 19 | 
             
                QueryDependenciesRecursively = ->(application, outlined_scope, scope) do
         | 
| @@ -34,7 +34,7 @@ module Holistic::Ruby::Scope | |
| 34 34 | 
             
                      .tap { dependencies.concat(_1) }
         | 
| 35 35 | 
             
                  end
         | 
| 36 36 |  | 
| 37 | 
            -
                  scope. | 
| 37 | 
            +
                  scope.lexical_children.map(&QueryDependenciesRecursively.curry[application, outlined_scope]).flatten.concat(dependencies)
         | 
| 38 38 | 
             
                end
         | 
| 39 39 |  | 
| 40 40 | 
             
                def call(application:, scope:)
         | 
| @@ -7,14 +7,20 @@ module Holistic::Ruby::Scope | |
| 7 7 | 
             
                def name                 = attr(:name)
         | 
| 8 8 | 
             
                def kind                 = attr(:kind)
         | 
| 9 9 |  | 
| 10 | 
            -
                def  | 
| 11 | 
            -
                def  | 
| 12 | 
            -
                def  | 
| 10 | 
            +
                def lexical_parent   = has_one(:lexical_parent)
         | 
| 11 | 
            +
                def lexical_children = has_many(:lexical_children)
         | 
| 12 | 
            +
                def ancestors        = has_many(:ancestors)
         | 
| 13 | 
            +
                def descendants      = has_many(:descendants)
         | 
| 14 | 
            +
                def referenced_by    = has_many(:referenced_by)
         | 
| 13 15 |  | 
| 14 16 | 
             
                def root?            = kind == Kind::ROOT
         | 
| 15 17 | 
             
                def class?           = kind == Kind::CLASS
         | 
| 16 18 | 
             
                def class_method?    = kind == Kind::CLASS_METHOD
         | 
| 17 19 | 
             
                def instance_method? = kind == Kind::INSTANCE_METHOD
         | 
| 18 20 | 
             
                def method?          = class_method? || instance_method?
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def inspect
         | 
| 23 | 
            +
                  "<#{self.class.name} kind=#{kind} fully_qualified_name=#{fully_qualified_name}>"
         | 
| 24 | 
            +
                end
         | 
| 19 25 | 
             
              end
         | 
| 20 26 | 
             
            end
         | 
| @@ -34,11 +34,15 @@ module Holistic::Ruby::Scope | |
| 34 34 |  | 
| 35 35 | 
             
                  return nil if file.nil?
         | 
| 36 36 |  | 
| 37 | 
            -
                  matching_scopes = file.defines_scopes. | 
| 38 | 
            -
                    scope.locations. | 
| 37 | 
            +
                  matching_scopes = file.defines_scopes.filter_map do |scope|
         | 
| 38 | 
            +
                    scope.locations.find { |location| location.body.contains?(cursor) }&.then do |location|
         | 
| 39 | 
            +
                      { location:, scope: }
         | 
| 40 | 
            +
                    end
         | 
| 39 41 | 
             
                  end
         | 
| 40 42 |  | 
| 41 | 
            -
                  matching_scopes.last
         | 
| 43 | 
            +
                  inner_most_matching_scope = matching_scopes.sort_by { |match| match[:location].declaration.start_line }.last
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  inner_most_matching_scope&.then { _1[:scope] }
         | 
| 42 46 | 
             
                end
         | 
| 43 47 |  | 
| 44 48 | 
             
                def list_scopes_in_file(file_path)
         | 
| @@ -4,32 +4,32 @@ module Holistic::Ruby::Scope | |
| 4 4 | 
             
              module Store
         | 
| 5 5 | 
             
                extend self
         | 
| 6 6 |  | 
| 7 | 
            -
                def call(database:,  | 
| 8 | 
            -
                  fully_qualified_name = build_fully_qualified_name( | 
| 7 | 
            +
                def call(database:, lexical_parent:, kind:, name:, location:)
         | 
| 8 | 
            +
                  fully_qualified_name = build_fully_qualified_name(lexical_parent:, kind:, name:)
         | 
| 9 9 |  | 
| 10 | 
            -
                   | 
| 10 | 
            +
                  scope = database.find(fully_qualified_name)
         | 
| 11 11 |  | 
| 12 | 
            -
                  if  | 
| 12 | 
            +
                  if scope.nil?
         | 
| 13 13 | 
             
                    record = Record.new(fully_qualified_name, { fully_qualified_name:, name:, kind:, locations: Location::Collection.new(name) })
         | 
| 14 14 |  | 
| 15 | 
            -
                     | 
| 15 | 
            +
                    scope = database.store(fully_qualified_name, record)
         | 
| 16 16 | 
             
                  end
         | 
| 17 17 |  | 
| 18 | 
            -
                   | 
| 18 | 
            +
                  scope.locations << location
         | 
| 19 19 |  | 
| 20 | 
            -
                   | 
| 21 | 
            -
                   | 
| 20 | 
            +
                  scope.relation(:lexical_parent).add!(lexical_parent)
         | 
| 21 | 
            +
                  scope.relation(:scope_defined_in_file).add!(location.declaration.file)
         | 
| 22 22 |  | 
| 23 | 
            -
                   | 
| 23 | 
            +
                  scope
         | 
| 24 24 | 
             
                end
         | 
| 25 25 |  | 
| 26 26 | 
             
                private
         | 
| 27 27 |  | 
| 28 | 
            -
                def build_fully_qualified_name( | 
| 28 | 
            +
                def build_fully_qualified_name(lexical_parent:, kind:, name:)
         | 
| 29 29 | 
             
                  parent_fully_qualified_name =
         | 
| 30 | 
            -
                    case  | 
| 30 | 
            +
                    case lexical_parent.kind
         | 
| 31 31 | 
             
                    when Kind::ROOT then ""
         | 
| 32 | 
            -
                    else  | 
| 32 | 
            +
                    else lexical_parent.fully_qualified_name
         | 
| 33 33 | 
             
                    end
         | 
| 34 34 |  | 
| 35 35 | 
             
                  separator =
         | 
| @@ -1,19 +1,28 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
               | 
| 5 | 
            -
                 | 
| 6 | 
            -
             | 
| 3 | 
            +
            module Holistic::Ruby::TypeInference
         | 
| 4 | 
            +
              class ProcessingQueue
         | 
| 5 | 
            +
                def initialize
         | 
| 6 | 
            +
                  @high_priority_queue = ::Queue.new
         | 
| 7 | 
            +
                  @queue = ::Queue.new
         | 
| 8 | 
            +
                end
         | 
| 7 9 |  | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 10 | 
            +
                def push(item)
         | 
| 11 | 
            +
                  @queue.push(item)
         | 
| 12 | 
            +
                end
         | 
| 11 13 |  | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 14 | 
            +
                def push_with_high_priority(item)
         | 
| 15 | 
            +
                  @high_priority_queue.push(item)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def empty?
         | 
| 19 | 
            +
                  @high_priority_queue.empty? && @queue.empty?
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def pop
         | 
| 23 | 
            +
                  return @high_priority_queue.pop if @high_priority_queue.size > 0
         | 
| 15 24 |  | 
| 16 | 
            -
             | 
| 17 | 
            -
                 | 
| 25 | 
            +
                  @queue.pop
         | 
| 26 | 
            +
                end
         | 
| 18 27 | 
             
              end
         | 
| 19 28 | 
             
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Holistic::Ruby::TypeInference::Resolver::Scope
         | 
| 4 | 
            +
              extend self
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def resolve(application:, nesting:, resolution_possibilities:)
         | 
| 7 | 
            +
                resolution_possibilities = ["::"] if nesting.root_scope_resolution?
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                resolution_possibilities.each do |resolution_candidate|
         | 
| 10 | 
            +
                  fully_qualified_scope_name =
         | 
| 11 | 
            +
                    if resolution_candidate == "::"
         | 
| 12 | 
            +
                      "::#{nesting.to_s}"
         | 
| 13 | 
            +
                    else
         | 
| 14 | 
            +
                      "#{resolution_candidate}::#{nesting.to_s}"
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  scope = application.scopes.find(fully_qualified_scope_name)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  return scope if scope.present?
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                nil
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -10,24 +10,27 @@ module Holistic::Ruby::TypeInference | |
| 10 10 | 
             
                    solve_method_call(application:, reference:)
         | 
| 11 11 |  | 
| 12 12 | 
             
                  if referenced_scope
         | 
| 13 | 
            -
                     | 
| 13 | 
            +
                    reference.relation(:referenced_scope).add!(referenced_scope)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    # NOTE: should this be an event that is handled by stdlib? I guess inheritance support with dedicated syntax
         | 
| 16 | 
            +
                    # is part of the language core, so it makes sense being here. Let me think about this for a bit.
         | 
| 17 | 
            +
                    reference.find_clue(Clue::ReferenceToSuperclass)&.then do |reference_to_superclass_clue|
         | 
| 18 | 
            +
                      referenced_scope.relation(:descendants).add!(reference_to_superclass_clue.subclass_scope)
         | 
| 19 | 
            +
                    end
         | 
| 14 20 | 
             
                  end
         | 
| 15 21 | 
             
                end
         | 
| 16 22 |  | 
| 17 23 | 
             
                private
         | 
| 18 24 |  | 
| 19 25 | 
             
                def solve_scope_reference(application:, reference:)
         | 
| 20 | 
            -
                   | 
| 21 | 
            -
                    reference.clues.one? && reference.clues.first.is_a?(Clue::ScopeReference)
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                  return unless has_scope_reference_clue
         | 
| 26 | 
            +
                  reference_to_scope_clue = reference.find_clue(Clue::ScopeReference)
         | 
| 24 27 |  | 
| 25 | 
            -
                   | 
| 28 | 
            +
                  return if reference_to_scope_clue.nil?
         | 
| 26 29 |  | 
| 27 | 
            -
                   | 
| 30 | 
            +
                  Resolver::Scope.resolve(
         | 
| 28 31 | 
             
                    application:,
         | 
| 29 | 
            -
                    nesting:  | 
| 30 | 
            -
                    resolution_possibilities:  | 
| 32 | 
            +
                    nesting: reference_to_scope_clue.nesting,
         | 
| 33 | 
            +
                    resolution_possibilities: reference_to_scope_clue.resolution_possibilities
         | 
| 31 34 | 
             
                  )
         | 
| 32 35 | 
             
                end
         | 
| 33 36 |  | 
| @@ -35,14 +38,14 @@ module Holistic::Ruby::TypeInference | |
| 35 38 | 
             
                  scope = reference.located_in_scope
         | 
| 36 39 |  | 
| 37 40 | 
             
                  if scope.class_method?
         | 
| 38 | 
            -
                     | 
| 39 | 
            -
                  elsif scope.instance_method? && scope. | 
| 40 | 
            -
                     | 
| 41 | 
            +
                    Resolver::ClassMethod.resolve(scope: scope.lexical_parent, method_name: method_call_clue.method_name)
         | 
| 42 | 
            +
                  elsif scope.instance_method? && scope.lexical_parent.present?
         | 
| 43 | 
            +
                    Resolver::InstanceMethod.resolve(scope: scope.lexical_parent, method_name: method_call_clue.method_name)
         | 
| 41 44 | 
             
                  end
         | 
| 42 45 | 
             
                end
         | 
| 43 46 |  | 
| 44 47 | 
             
                SolveMethodCallInSpecifiedScope = ->(application:, reference:, method_call_clue:) do
         | 
| 45 | 
            -
                  referenced_scope =  | 
| 48 | 
            +
                  referenced_scope = Resolver::Scope.resolve(
         | 
| 46 49 | 
             
                    application:,
         | 
| 47 50 | 
             
                    nesting: method_call_clue.nesting,
         | 
| 48 51 | 
             
                    resolution_possibilities: method_call_clue.resolution_possibilities
         | 
| @@ -52,14 +55,7 @@ module Holistic::Ruby::TypeInference | |
| 52 55 |  | 
| 53 56 | 
             
                  referenced_method = application.extensions.dispatch(:resolve_method_call_known_scope, { reference:, referenced_scope:, method_call_clue: })
         | 
| 54 57 |  | 
| 55 | 
            -
                  referenced_method ||  | 
| 56 | 
            -
                end
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                SolveMethodCallInLocalVariable = ->(application:, reference:, method_call_clue:) do
         | 
| 59 | 
            -
                  # local_variable_name = method_call_clue.nesting.to_s
         | 
| 60 | 
            -
                  # referenced_scope = guess_scope_for_local_variable(scope: reference.scope, name: local_variable_name)
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                  nil
         | 
| 58 | 
            +
                  referenced_method || Resolver::ClassMethod.resolve(scope: referenced_scope, method_name: method_call_clue.method_name)
         | 
| 63 59 | 
             
                end
         | 
| 64 60 |  | 
| 65 61 | 
             
                def solve_method_call(application:, reference:)
         | 
| @@ -74,39 +70,8 @@ module Holistic::Ruby::TypeInference | |
| 74 70 | 
             
                  elsif method_call_clue.nesting.constant?
         | 
| 75 71 | 
             
                    SolveMethodCallInSpecifiedScope.call(application:, reference:, method_call_clue:)
         | 
| 76 72 | 
             
                  else
         | 
| 77 | 
            -
                     | 
| 78 | 
            -
                  end
         | 
| 79 | 
            -
                end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                def resolve_scope(application:, nesting:, resolution_possibilities:)
         | 
| 82 | 
            -
                  resolution_possibilities = ["::"] if nesting.root_scope_resolution?
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                  resolution_possibilities.each do |resolution_candidate|
         | 
| 85 | 
            -
                    fully_qualified_scope_name =
         | 
| 86 | 
            -
                      if resolution_candidate == "::"
         | 
| 87 | 
            -
                        "::#{nesting.to_s}"
         | 
| 88 | 
            -
                      else
         | 
| 89 | 
            -
                        "#{resolution_candidate}::#{nesting.to_s}"
         | 
| 90 | 
            -
                      end
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                    scope = application.scopes.find(fully_qualified_scope_name)
         | 
| 93 | 
            -
             | 
| 94 | 
            -
                    return scope if scope.present?
         | 
| 73 | 
            +
                    nil # TODO
         | 
| 95 74 | 
             
                  end
         | 
| 96 | 
            -
             | 
| 97 | 
            -
                  nil
         | 
| 98 | 
            -
                end
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                def resolve_instance_method(application:, scope:, method_name:)
         | 
| 101 | 
            -
                  method_fully_qualified_name = "#{scope.fully_qualified_name}##{method_name}"
         | 
| 102 | 
            -
             | 
| 103 | 
            -
                  application.scopes.find(method_fully_qualified_name)
         | 
| 104 | 
            -
                end
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                def resolve_class_method(application:, scope:, method_name:)
         | 
| 107 | 
            -
                  method_fully_qualified_name = "#{scope.fully_qualified_name}.#{method_name}"
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                  application.scopes.find(method_fully_qualified_name)
         | 
| 110 75 | 
             
                end
         | 
| 111 76 | 
             
              end
         | 
| 112 77 | 
             
            end
         | 
    
        data/lib/holistic/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: holistic-ruby
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.7
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Luiz Vasconcellos
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2023- | 
| 11 | 
            +
            date: 2023-09-07 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: syntax_tree
         | 
| @@ -73,9 +73,10 @@ files: | |
| 73 73 | 
             
            - lib/holistic.rb
         | 
| 74 74 | 
             
            - lib/holistic/application.rb
         | 
| 75 75 | 
             
            - lib/holistic/background_process.rb
         | 
| 76 | 
            +
            - lib/holistic/database.rb
         | 
| 76 77 | 
             
            - lib/holistic/database/migrations.rb
         | 
| 77 78 | 
             
            - lib/holistic/database/node.rb
         | 
| 78 | 
            -
            - lib/holistic/database/ | 
| 79 | 
            +
            - lib/holistic/database/relation.rb
         | 
| 79 80 | 
             
            - lib/holistic/document/cursor.rb
         | 
| 80 81 | 
             
            - lib/holistic/document/file/record.rb
         | 
| 81 82 | 
             
            - lib/holistic/document/file/repository.rb
         | 
| @@ -122,6 +123,8 @@ files: | |
| 122 123 | 
             
            - lib/holistic/ruby/scope/delete.rb
         | 
| 123 124 | 
             
            - lib/holistic/ruby/scope/kind.rb
         | 
| 124 125 | 
             
            - lib/holistic/ruby/scope/lexical.rb
         | 
| 126 | 
            +
            - lib/holistic/ruby/scope/list_class_methods.rb
         | 
| 127 | 
            +
            - lib/holistic/ruby/scope/list_instance_methods.rb
         | 
| 125 128 | 
             
            - lib/holistic/ruby/scope/list_references.rb
         | 
| 126 129 | 
             
            - lib/holistic/ruby/scope/location.rb
         | 
| 127 130 | 
             
            - lib/holistic/ruby/scope/outline.rb
         | 
| @@ -129,8 +132,12 @@ files: | |
| 129 132 | 
             
            - lib/holistic/ruby/scope/repository.rb
         | 
| 130 133 | 
             
            - lib/holistic/ruby/scope/store.rb
         | 
| 131 134 | 
             
            - lib/holistic/ruby/type_inference/clue/method_call.rb
         | 
| 135 | 
            +
            - lib/holistic/ruby/type_inference/clue/reference_to_superclass.rb
         | 
| 132 136 | 
             
            - lib/holistic/ruby/type_inference/clue/scope_reference.rb
         | 
| 133 137 | 
             
            - lib/holistic/ruby/type_inference/processing_queue.rb
         | 
| 138 | 
            +
            - lib/holistic/ruby/type_inference/resolver/class_method.rb
         | 
| 139 | 
            +
            - lib/holistic/ruby/type_inference/resolver/instance_method.rb
         | 
| 140 | 
            +
            - lib/holistic/ruby/type_inference/resolver/scope.rb
         | 
| 134 141 | 
             
            - lib/holistic/ruby/type_inference/solve.rb
         | 
| 135 142 | 
             
            - lib/holistic/ruby/type_inference/solve_pending_references.rb
         | 
| 136 143 | 
             
            - lib/holistic/version.rb
         | 
| @@ -1,78 +0,0 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            # frozen_string_literal: true
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            module Holistic::Database
         | 
| 5 | 
            -
              class Table
         | 
| 6 | 
            -
                attr_reader :records, :connections
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                def initialize
         | 
| 9 | 
            -
                  @records = ::Hash.new
         | 
| 10 | 
            -
                  @connections = ::Hash.new
         | 
| 11 | 
            -
                end
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                def define_connection(name:, inverse_of:)
         | 
| 14 | 
            -
                  raise ::ArgumentError if @connections.key?(name) || @connections.key?(inverse_of)
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                  @connections[name] = { inverse_of: }
         | 
| 17 | 
            -
                  @connections[inverse_of] = { inverse_of: name }
         | 
| 18 | 
            -
                end
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                def store(id, node_or_attrs)
         | 
| 21 | 
            -
                  if @records.key?(id)
         | 
| 22 | 
            -
                    return @records[id]&.tap do |node|
         | 
| 23 | 
            -
                      node.attributes =
         | 
| 24 | 
            -
                        case node_or_attrs
         | 
| 25 | 
            -
                        in ::Hash then node_or_attrs
         | 
| 26 | 
            -
                        in Node   then node_or_attrs.attributes
         | 
| 27 | 
            -
                        end
         | 
| 28 | 
            -
                    end
         | 
| 29 | 
            -
                  end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                  node =
         | 
| 32 | 
            -
                    case node_or_attrs
         | 
| 33 | 
            -
                    in ::Hash then Node.new(id, node_or_attrs)
         | 
| 34 | 
            -
                    in Node   then node_or_attrs
         | 
| 35 | 
            -
                    end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                  node.__set_database__(self)
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                  @records[id] = node
         | 
| 40 | 
            -
                end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                def connect(source:, target:, name:, inverse_of:)
         | 
| 43 | 
            -
                  connection = @connections[name]
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                  raise ::ArgumentError if connection.nil? || connection[:inverse_of] != inverse_of
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                  source.connections[name].add(target)
         | 
| 48 | 
            -
                  target.connections[inverse_of].add(source)
         | 
| 49 | 
            -
                end
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                def disconnect(source:, target:, name:, inverse_of:)
         | 
| 52 | 
            -
                  connection = @connections[name]
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                  raise ::ArgumentError if connection.nil? || connection[:inverse_of] != inverse_of
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                  source.connections[name].delete(target)
         | 
| 57 | 
            -
                  target.connections[inverse_of].delete(source)
         | 
| 58 | 
            -
                end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                def find(id)
         | 
| 61 | 
            -
                  @records[id]
         | 
| 62 | 
            -
                end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                def delete(id)
         | 
| 65 | 
            -
                  records.delete(id)
         | 
| 66 | 
            -
                end
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                concerning :TestHelpers do
         | 
| 69 | 
            -
                  def all
         | 
| 70 | 
            -
                    records.values
         | 
| 71 | 
            -
                  end
         | 
| 72 | 
            -
             | 
| 73 | 
            -
                  def size
         | 
| 74 | 
            -
                    records.size
         | 
| 75 | 
            -
                  end
         | 
| 76 | 
            -
                end
         | 
| 77 | 
            -
              end
         | 
| 78 | 
            -
            end
         |