diff --git a/.vscode/launch.json b/.vscode/launch.json index 0aecd80..a0047db 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,75 +5,13 @@ "version": "0.2.0", "configurations": [ { - "name": "Launch index.html", - "type": "chrome", + "type": "rdbg", + "name": "Rspec", "request": "launch", - "breakOnLoad": true, - "sourceMapPathOverrides": { - "webRoot": "${workspaceRoot}" - }, - "file": "${workspaceRoot}/index.html" - }, - { - "name": "Debug Local File", - "type": "Ruby", - "request": "launch", - "cwd": "${workspaceRoot}", - "program": "${workspaceRoot}/exe/cpp_dependency_graph", - "useBundler": true, - "pathToBundler": "C:/tools/ruby25/bin/bundle.bat", - "pathToRDebugIDE": "C:/tools/ruby25/bin/rdebug-ide.bat", - "showDebuggerOutput": true, - "args": ["visualise_components", "-r", "../TileDB"] - }, - { - "name": "Listen for rdebug-ide", - "type": "Ruby", - "request": "attach", - "cwd": "${workspaceRoot}", - "remoteHost": "127.0.0.1", - "remotePort": "1234", - "remoteWorkspaceRoot": "${workspaceRoot}" - }, - { - "name": "Rails server", - "type": "Ruby", - "request": "launch", - "cwd": "${workspaceRoot}", - "program": "${workspaceRoot}/bin/rails", - "args": [ - "server" - ] - }, - { - "name": "RSpec - all", - "type": "Ruby", - "request": "launch", - "cwd": "${workspaceRoot}", - "program": "C:/tools/ruby25/bin/rspec", - "args": [ - "-I", - "${workspaceRoot}" - ] - }, - { - "name": "RSpec - active spec file only", - "type": "Ruby", - "request": "launch", - "cwd": "${workspaceRoot}", - "program": "C:/tools/ruby25/bin/rspec", - "args": [ - "-I", - "${workspaceRoot}", - "${file}" - ] - }, - { - "name": "Cucumber", - "type": "Ruby", - "request": "launch", - "cwd": "${workspaceRoot}", - "program": "${workspaceRoot}/bin/cucumber" + "command": "bundle exec rspec", + "script": "${file}:${lineNumber}", + "args": [], + "askParameters": false } ] -} \ No newline at end of file +} diff --git a/lib/cpp_dependency_graph/include_component_dependency_graph.rb b/lib/cpp_dependency_graph/include_component_dependency_graph.rb index b623d55..a542eaf 100644 --- a/lib/cpp_dependency_graph/include_component_dependency_graph.rb +++ b/lib/cpp_dependency_graph/include_component_dependency_graph.rb @@ -23,7 +23,6 @@ def all_cyclic_links def links(component_name) component = @project.source_component(component_name) - p component source_files = component.source_files external_includes = @project.external_includes(component) source_files.map do |file| diff --git a/lib/cpp_dependency_graph/include_dependency.rb b/lib/cpp_dependency_graph/include_dependency.rb new file mode 100644 index 0000000..c4b3734 --- /dev/null +++ b/lib/cpp_dependency_graph/include_dependency.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Represents a #include in a source file +class IncludeDependency + def initialize(raw_include) + @raw_include = raw_include + p = Pathname.new(@raw_include) + @dir_name = p.dirname + @basename = p.basename.to_s + @absolute_include = @dir_name == Pathname.new('.') + end + + def relative? + !@absolute_include + end + + def absolute? + @absolute_include + end + + def component + return @dir_name.to_s unless absolute? + + '' + end + + def ==(other) + raw_include == other.raw_include + end + alias eql? == + + def hash + [self.class, @raw_include].hash + end + + attr_reader :basename, :raw_include +end diff --git a/lib/cpp_dependency_graph/include_to_component_resolver.rb b/lib/cpp_dependency_graph/include_to_component_resolver.rb index 4e8f5f4..8b00926 100644 --- a/lib/cpp_dependency_graph/include_to_component_resolver.rb +++ b/lib/cpp_dependency_graph/include_to_component_resolver.rb @@ -18,9 +18,12 @@ def external_includes(component) end def component_for_include(include) - return '' unless source_files.key?(include) + return '' unless source_files.key?(include.basename) - @component_include_map_cache[include] = component_for_include_private(include) unless @component_include_map_cache.key?(include) + component = component_for_include_private(include.basename) unless @component_include_map_cache.key?(include.basename) + return '' if !include.component.empty? and include.component != component + + @component_include_map_cache[include] = component @component_include_map_cache[include] end diff --git a/lib/cpp_dependency_graph/project.rb b/lib/cpp_dependency_graph/project.rb index 1cdc637..b00d714 100644 --- a/lib/cpp_dependency_graph/project.rb +++ b/lib/cpp_dependency_graph/project.rb @@ -37,7 +37,10 @@ def source_files def dependencies(component) # TODO: This is repeating the same work twice! component_for_include is called when calling external_includes - external_includes(component).map { |include| @include_resolver.component_for_include(include) }.reject(&:empty?).uniq + external_includes_component = external_includes(component) + external_includes_component.map do |include| + @include_resolver.component_for_include(include) + end.reject(&:empty?).uniq end def external_includes(component) diff --git a/lib/cpp_dependency_graph/source_component.rb b/lib/cpp_dependency_graph/source_component.rb index 8658984..151dc22 100644 --- a/lib/cpp_dependency_graph/source_component.rb +++ b/lib/cpp_dependency_graph/source_component.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true +require 'pathname' + require_relative 'config' +require_relative 'include_dependency' require_relative 'directory_parser' require_relative 'source_file' @@ -24,13 +27,17 @@ def source_files end def includes - @includes ||= source_files.flat_map(&:includes).uniq.map { |include| File.basename(include) } + @includes ||= source_files.flat_map(&:includes).uniq.map { |f| IncludeDependency.new(f) }.uniq end def loc @loc ||= source_files.inject(0) { |total_loc, file| total_loc + file.loc } end + def exists? + File.exist?(@path) + end + private def parse_source_files(extensions) diff --git a/lib/cpp_dependency_graph/source_file.rb b/lib/cpp_dependency_graph/source_file.rb index 9fa9feb..9b966b6 100644 --- a/lib/cpp_dependency_graph/source_file.rb +++ b/lib/cpp_dependency_graph/source_file.rb @@ -34,6 +34,10 @@ def loc @loc ||= file_contents.lines.count end + def exists? + File.exist?(path) + end + private def all_includes diff --git a/spec/test/example_project/Engine/Engine.cpp b/spec/test/example_project/Engine/Engine.cpp index 8076f7a..a1f0123 100644 --- a/spec/test/example_project/Engine/Engine.cpp +++ b/spec/test/example_project/Engine/Engine.cpp @@ -1,3 +1,2 @@ #include "DataAccess/DA.h" #include "Engine.h" - diff --git a/spec/test/include_dependency_spec.rb b/spec/test/include_dependency_spec.rb new file mode 100644 index 0000000..70b780a --- /dev/null +++ b/spec/test/include_dependency_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'cpp_dependency_graph/include_dependency' + +RSpec.describe IncludeDependency do + it 'relative? returns false for an absolute include' do + i = IncludeDependency.new('Engine.h') + expect(i.relative?).to be false + expect(i.absolute?).to be true + end + + it 'relative? returns true for a relative include' do + i = IncludeDependency.new('Engine/Engine.h') + expect(i.relative?).to be true + expect(i.absolute?).to be false + end + + it 'component returns empty for a relative include' do + i = IncludeDependency.new('Engine.h') + expect(i.component).to eq('') + end + + it 'component returns parent directory for a relative include' do + i = IncludeDependency.new('Engine/Engine.h') + expect(i.component).to eq('Engine') + end + + it 'returns the correct basename' do + i = IncludeDependency.new('Engine/Engine.h') + expect(i.basename).to eq('Engine.h') + end + + it 'compares correctly against other instances' do + i1 = IncludeDependency.new('Engine/Engine.h') + i2 = IncludeDependency.new('Engine/Engine.h') + expect(i1).to eq(i2) + end +end diff --git a/spec/test/project_spec.rb b/spec/test/project_spec.rb index 21e03ee..94d48e8 100644 --- a/spec/test/project_spec.rb +++ b/spec/test/project_spec.rb @@ -25,17 +25,17 @@ expect(component.source_files.size).to eq(0) end - it 'returns dependencies of component' do - project = Project.new('spec/test/example_project') - component = project.source_component('Engine') - dependencies = project.dependencies(component) - expect(dependencies).to include('Framework', 'UI', 'DataAccess') - end - it 'all source files of project' do project = Project.new('spec/test/example_project') source_files = project.source_files.values.map(&:basename) expect(source_files).to contain_exactly('DA.h', 'Display.cpp', 'Display.h', 'Engine.cpp', 'Engine.h', 'Engine.h', 'OldEngine.h', 'System.cpp', 'System.h', 'framework.h', 'main.cpp') end + + it 'returns dependencies of component' do + project = Project.new('spec/test/example_project') + component = project.source_component('Engine') + dependencies = project.dependencies(component) + expect(dependencies).to include('Framework', 'UI', 'DataAccess') + end end diff --git a/spec/test/source_component_spec.rb b/spec/test/source_component_spec.rb index dd60f66..d759ce2 100644 --- a/spec/test/source_component_spec.rb +++ b/spec/test/source_component_spec.rb @@ -3,6 +3,16 @@ require 'cpp_dependency_graph/source_component' RSpec.describe SourceComponent do + it 'exists? returns true for a valid component' do + component = SourceComponent.new('spec/test/example_project/Engine') + expect(component.exists?).to be true + end + + it 'exists? returns false for a valid component' do + component = SourceComponent.new('spec/test/example_project/Engine2') + expect(component.exists?).to be false + end + it 'has a name attribute that matches the directory' do component = SourceComponent.new('spec/test/example_project/Engine') expect(component.name).to eq('Engine') @@ -18,9 +28,11 @@ expect(source_file_names).to contain_exactly('Engine.cpp', 'Engine.h', 'OldEngine.h') end - it 'has an includes attribute' do + it 'outputs the correct includes for component' do component = SourceComponent.new('spec/test/example_project/Engine') - expect(component.includes).to contain_exactly('framework.h', 'Display.h', 'DA.h', 'Engine.h') + expect(component.includes).to contain_exactly(an_object_having_attributes(basename: 'framework.h'), + an_object_having_attributes(basename: 'Display.h'), an_object_having_attributes(basename: 'DA.h'), + an_object_having_attributes(basename: 'Engine.h')) end it 'has a loc (lines of code) attribute' do diff --git a/spec/test/source_file_spec.rb b/spec/test/source_file_spec.rb index 7fa1ae2..3dd764a 100644 --- a/spec/test/source_file_spec.rb +++ b/spec/test/source_file_spec.rb @@ -32,4 +32,14 @@ source_file = SourceFile.new('spec/test/example_project/Engine/Engine.cpp') expect(source_file.loc).to be > 0 end + + it 'exists? returns true for a file that exists' do + source_file = SourceFile.new('spec/test/example_project/Engine/Engine.cpp') + expect(source_file.exists?).to be true + end + + it 'exists? returns false for a file that does not exist' do + source_file = SourceFile.new('spec/test/example_project/Engine/Engine2.cpp') + expect(source_file.exists?).to be false + end end