Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Circular dependencies #4 #5

Closed
wants to merge 12 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
tmp/
pkg/
.DS_Store
.DS_Store
.idea
.byebug_history
2 changes: 2 additions & 0 deletions .hound.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ruby:
config_file: .rubocop.yml
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Metrics/BlockLength:
Exclude:
- 'spec/**/*_spec.rb'
Style/Documentation:
Enabled: false
Style/FrozenStringLiteralComment:
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
source "https://rubygems.org"
source 'https://rubygems.org'
gemspec
19 changes: 18 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -8,17 +8,34 @@ GEM
remote: https://rubygems.org/
specs:
ast (2.2.0)
byebug (9.0.6)
diff-lcs (1.3)
parser (2.4.0.0)
ast (~> 2.2)
rake (10.4.2)
rspec (3.6.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-core (3.6.0)
rspec-support (~> 3.6.0)
rspec-expectations (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0)
rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0)
rspec-support (3.6.0)

PLATFORMS
ruby

DEPENDENCIES
bundler (~> 1.14)
byebug
rake (~> 10.0)
rspec
rubrowser!

BUNDLED WITH
1.14.5
1.14.6
17 changes: 10 additions & 7 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
require "bundler/gem_tasks"
require "rake/testtask"
require 'bundler/gem_tasks'

Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList['test/**/*_test.rb']
task :s do
ruby './bin/rubrowser'
end

task default: :test
begin
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

task default: :spec
end
5 changes: 3 additions & 2 deletions bin/rubrowser
Original file line number Diff line number Diff line change
@@ -8,12 +8,13 @@ require 'rubrowser/server'
OPTIONS = {
port: 9000,
files: ARGV.empty? ? ['.'] : ARGV
}
}.freeze

OptionParser.new do |opts|
opts.banner = "Usage: #{__FILE__} [options] [file] ..."

opts.on('-pPORT', '--port=PORT', 'Specify port number for server, default = 9000') do |port|
message = 'Specify port number for server, default = 9000'
opts.on('-pPORT', '--port=PORT', message) do |port|
OPTIONS[:port] = port.to_i
end

23 changes: 23 additions & 0 deletions lib/rubrowser/a.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class A

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing top-level class documentation comment.
Missing magic comment # frozen_string_literal: true.

def initialize
B.do
end

def self.do; end
end

class B

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing top-level class documentation comment.

def initialize
C.do
end

def self.do; end
end

class C

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing top-level class documentation comment.

def initialize
A.do
end

def self.do; end
end
56 changes: 49 additions & 7 deletions lib/rubrowser/data.rb
Original file line number Diff line number Diff line change
@@ -1,30 +1,72 @@
require 'rubrowser/parser/factory'
require 'tsort'

module Rubrowser
class Data
attr_reader :definitions, :relations

def initialize(files)
@files = files
@parsed = false
parse
end

def definitions
@_definitions ||= parsers.map(&:definitions).reduce(:+).to_a
def mark_circular_dependencies
mark_circular_components(components)
end

def mark_circular_components(components)
@definitions.each do |definition|
if components.include?(definition.namespace.first.to_s)
definition.set_circular
end
end

@relations.each do |relation|
relation.set_circular if components.include?(relation.namespace.to_s)
end
end

def components
graph = Graph.new { |h, k| h[k] = [] }

@relations.each do |relation|
graph[relation.caller_namespace.to_s] <<
relation.resolve(definitions).to_s
end

find_coupled_components(graph)
end

def relations
@_relations ||= parsers.map(&:relations).reduce(:+).to_a
def find_coupled_components(graph)
graph
.strongly_connected_components
.select { |c| c.length > 1 }
.flatten
.to_set
end

private

class Graph < Hash

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing top-level class documentation comment.

include TSort

alias tsort_each_node each_key

def tsort_each_child(node, &block)
fetch(node) { [] }.each(&block)
end
end

attr_reader :files, :parsed
alias parsed? parsed

def parse
return if parsed?
parsers.each(&:parse)
@parsed = true

@definitions ||= parsers.map(&:definitions).reduce(:+).to_a
@relations ||= parsers.map(&:relations).reduce(:+).to_a

mark_circular_dependencies
end

def parsers
6 changes: 5 additions & 1 deletion lib/rubrowser/formatter/json.rb
Original file line number Diff line number Diff line change
@@ -10,7 +10,9 @@ def initialize(data)
def call
{
definitions: data.definitions.map { |d| definition_as_json(d) },
relations: data.relations.map { |r| relation_as_json(r, data.definitions) }
relations: data.relations.map do |r|
relation_as_json(r, data.definitions)
end
}.to_json
end

@@ -22,6 +24,7 @@ def definition_as_json(definition)
{
type: demoularize(definition.class.name),
namespace: definition.to_s,
circular: definition.circular?,
file: definition.file,
line: definition.line,
lines: definition.lines
@@ -35,6 +38,7 @@ def relation_as_json(relation, definitions)
resolved_namespace: relation.resolve(definitions).to_s,
caller: relation.caller_namespace.to_s,
file: relation.file,
circular: relation.circular?,
line: relation.line
}
end
11 changes: 10 additions & 1 deletion lib/rubrowser/parser/definition/base.rb
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ def initialize(namespace, file: nil, line: nil, lines: 0)
@file = file
@line = line
@lines = lines
@circular = false
end

def name
@@ -23,8 +24,16 @@ def kernel?
namespace.empty?
end

def circular?
@circular
end

def set_circular
@circular = true
end

def ==(other)
namespace == other.namespace
namespace == other.namespace && circular? == other.circular?
end

def to_s
42 changes: 24 additions & 18 deletions lib/rubrowser/parser/file.rb
Original file line number Diff line number Diff line change
@@ -19,22 +19,29 @@ def initialize(file)

def parse
return unless valid_file?(file)
constants = constants_from_file

@definitions = constants[:definitions]
@relations = constants[:relations]
rescue ::Parser::SyntaxError
warn "SyntaxError in #{file}"
end

def constants_from_file
contents = ::File.read(file)

buffer = ::Parser::Source::Buffer.new(file, 1)
buffer.source = contents.force_encoding(Encoding::UTF_8)

ast = parser.parse(buffer)
parse_block(ast)
end

def parser
parser = ::Parser::CurrentRuby.new(Builder.new)
parser.diagnostics.ignore_warnings = true
parser.diagnostics.all_errors_are_fatal = false

ast = parser.parse(buffer)
constants = parse_block(ast)

@definitions = constants[:definitions]
@relations = constants[:relations]
rescue ::Parser::SyntaxError
warn "SyntaxError in #{file}"
parser
end

def valid_file?(file)
@@ -58,26 +65,25 @@ def parse_block(node, parents = [])

def parse_module(node, parents = [])
namespace = ast_consts_to_array(node.children.first, parents)
definition = Definition::Module.new(
namespace,
file: file,
line: node.loc.line,
lines: node.loc.last_line - node.loc.line + 1
)
definition = build_definition(Definition::Module, namespace, node)
constants = { definitions: [definition] }
children_constants = parse_array(node.children[1..-1], namespace)

merge_constants(children_constants, constants)
end

def parse_class(node, parents = [])
namespace = ast_consts_to_array(node.children.first, parents)
definition = Definition::Class.new(
def build_definition(klass, namespace, node)
klass.new(
namespace,
file: file,
line: node.loc.line,
lines: node.loc.last_line - node.loc.line + 1
)
end

def parse_class(node, parents = [])
namespace = ast_consts_to_array(node.children.first, parents)
definition = build_definition(Definition::Class, namespace, node)
constants = { definitions: [definition] }
children_constants = parse_array(node.children[1..-1], namespace)

@@ -111,7 +117,7 @@ def merge_constants(c1, c2)

def ast_consts_to_array(node, parents = [])
return parents unless valid_node?(node) &&
[:const, :cbase].include?(node.type)
%I[const cbase].include?(node.type)
ast_consts_to_array(node.children.first, parents) + [node.children.last]
end

12 changes: 11 additions & 1 deletion lib/rubrowser/parser/relation/base.rb
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ def initialize(namespace, caller_namespace, file: nil, line: nil)
@caller_namespace = caller_namespace
@file = file
@line = line
@is_circular = false
end

def namespace
@@ -21,6 +22,14 @@ def caller_namespace
Definition::Base.new(@caller_namespace, file: file, line: line)
end

def circular?
@is_circular
end

def set_circular
@is_circular = true
end

def resolve(definitions)
possibilities.find do |possibility|
definitions.any? { |definition| definition == possibility }
@@ -29,7 +38,8 @@ def resolve(definitions)

def ==(other)
namespace == other.namespace &&
caller_namespace == other.caller_namespace
caller_namespace == other.caller_namespace &&
circular? == other.circular?
end

private
1 change: 1 addition & 0 deletions lib/rubrowser/server.rb
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
require 'erb'
require 'rubrowser/data'
require 'rubrowser/formatter/json'
require 'byebug'

module Rubrowser
class Server < WEBrick::HTTPServer
4 changes: 4 additions & 0 deletions public/css/application.css
Original file line number Diff line number Diff line change
@@ -27,6 +27,10 @@ circle {
stroke-width: 1.5px;
}

.circular {
stroke: #FF0000;
}

.fixed circle {
stroke-width: 3px;
}
Loading