├── .github └── workflows │ ├── ci-workflow.yml │ └── smoke-test.yml ├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── .ruby-gemset ├── .ruby-version ├── .simplecov ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── TODOS.md ├── bin └── inch ├── config ├── .inch.yml.sample ├── base.rb ├── elixir.rb ├── javascript.rb └── ruby.rb ├── inch.gemspec ├── lib ├── inch.rb └── inch │ ├── api.rb │ ├── api │ ├── compare.rb │ ├── compare │ │ ├── code_objects.rb │ │ └── codebases.rb │ ├── diff.rb │ ├── filter.rb │ ├── get.rb │ ├── list.rb │ ├── options │ │ ├── base.rb │ │ ├── filter.rb │ │ └── suggest.rb │ ├── stats.rb │ └── suggest.rb │ ├── cli.rb │ ├── cli │ ├── arguments.rb │ ├── command.rb │ ├── command │ │ ├── base.rb │ │ ├── base_list.rb │ │ ├── base_object.rb │ │ ├── console.rb │ │ ├── diff.rb │ │ ├── inspect.rb │ │ ├── list.rb │ │ ├── options │ │ │ ├── base.rb │ │ │ ├── base_list.rb │ │ │ ├── base_object.rb │ │ │ ├── console.rb │ │ │ ├── diff.rb │ │ │ ├── inspect.rb │ │ │ ├── list.rb │ │ │ ├── show.rb │ │ │ ├── stats.rb │ │ │ └── suggest.rb │ │ ├── output │ │ │ ├── base.rb │ │ │ ├── console.rb │ │ │ ├── diff.rb │ │ │ ├── inspect.rb │ │ │ ├── list.rb │ │ │ ├── show.rb │ │ │ ├── stats.rb │ │ │ └── suggest.rb │ │ ├── show.rb │ │ ├── stats.rb │ │ └── suggest.rb │ ├── command_parser.rb │ ├── sparkline_helper.rb │ ├── trace_helper.rb │ └── yardopts_helper.rb │ ├── code_object.rb │ ├── code_object │ ├── converter.rb │ ├── provider.rb │ └── proxy.rb │ ├── codebase.rb │ ├── codebase │ ├── object.rb │ ├── objects.rb │ ├── objects_filter.rb │ ├── proxy.rb │ └── serializer.rb │ ├── config.rb │ ├── config │ ├── base.rb │ ├── codebase.rb │ └── evaluation.rb │ ├── core_ext.rb │ ├── core_ext │ └── string.rb │ ├── evaluation.rb │ ├── evaluation │ ├── file.rb │ ├── grade.rb │ ├── grade_list.rb │ ├── priority_range.rb │ ├── proxy.rb │ └── role.rb │ ├── language.rb │ ├── language │ ├── elixir │ │ ├── code_object │ │ │ ├── base.rb │ │ │ ├── function_object.rb │ │ │ ├── function_parameter_object.rb │ │ │ ├── module_object.rb │ │ │ └── type_object.rb │ │ ├── evaluation │ │ │ ├── base.rb │ │ │ ├── function_object.rb │ │ │ ├── module_object.rb │ │ │ └── type_object.rb │ │ ├── import.rb │ │ ├── provider │ │ │ ├── parser.rb │ │ │ ├── reader.rb │ │ │ ├── reader │ │ │ │ ├── docstring.rb │ │ │ │ ├── object.rb │ │ │ │ └── object │ │ │ │ │ ├── base.rb │ │ │ │ │ ├── function_object.rb │ │ │ │ │ ├── function_parameter_object.rb │ │ │ │ │ ├── module_object.rb │ │ │ │ │ └── type_object.rb │ │ │ └── reader_v2 │ │ │ │ ├── docstring.rb │ │ │ │ ├── object.rb │ │ │ │ └── object │ │ │ │ ├── base.rb │ │ │ │ ├── function_object.rb │ │ │ │ ├── function_parameter_object.rb │ │ │ │ ├── module_object.rb │ │ │ │ └── type_object.rb │ │ └── roles │ │ │ ├── base.rb │ │ │ ├── function.rb │ │ │ ├── function_parameter.rb │ │ │ ├── module.rb │ │ │ ├── object.rb │ │ │ └── type.rb │ ├── javascript │ │ ├── code_object │ │ │ ├── base.rb │ │ │ ├── class_object.rb │ │ │ ├── function_object.rb │ │ │ ├── function_parameter_object.rb │ │ │ ├── member_object.rb │ │ │ └── module_object.rb │ │ ├── evaluation │ │ │ ├── base.rb │ │ │ ├── class_object.rb │ │ │ ├── function_object.rb │ │ │ ├── member_object.rb │ │ │ └── module_object.rb │ │ ├── import.rb │ │ ├── provider │ │ │ ├── jsdoc.rb │ │ │ └── jsdoc │ │ │ │ ├── docstring.rb │ │ │ │ ├── object.rb │ │ │ │ ├── object │ │ │ │ ├── base.rb │ │ │ │ ├── class_object.rb │ │ │ │ ├── function_object.rb │ │ │ │ ├── function_parameter_object.rb │ │ │ │ ├── member_object.rb │ │ │ │ └── module_object.rb │ │ │ │ └── parser.rb │ │ └── roles │ │ │ ├── base.rb │ │ │ ├── function.rb │ │ │ ├── function_parameter.rb │ │ │ ├── member.rb │ │ │ ├── module.rb │ │ │ └── object.rb │ └── ruby │ │ ├── code_object │ │ ├── base.rb │ │ ├── class_object.rb │ │ ├── class_variable_object.rb │ │ ├── constant_object.rb │ │ ├── method_object.rb │ │ ├── method_parameter_object.rb │ │ ├── module_object.rb │ │ └── namespace_object.rb │ │ ├── evaluation │ │ ├── base.rb │ │ ├── class_object.rb │ │ ├── class_variable_object.rb │ │ ├── constant_object.rb │ │ ├── method_object.rb │ │ ├── module_object.rb │ │ └── namespace_object.rb │ │ ├── import.rb │ │ ├── provider │ │ ├── yard.rb │ │ └── yard │ │ │ ├── docstring.rb │ │ │ ├── nodoc_helper.rb │ │ │ ├── object.rb │ │ │ ├── object │ │ │ ├── base.rb │ │ │ ├── class_object.rb │ │ │ ├── class_variable_object.rb │ │ │ ├── constant_object.rb │ │ │ ├── method_object.rb │ │ │ ├── method_parameter_object.rb │ │ │ ├── method_signature.rb │ │ │ ├── module_object.rb │ │ │ ├── namespace_object.rb │ │ │ └── root_object.rb │ │ │ └── parser.rb │ │ └── roles │ │ ├── base.rb │ │ ├── class_variable.rb │ │ ├── constant.rb │ │ ├── method.rb │ │ ├── method_parameter.rb │ │ ├── missing.rb │ │ ├── namespace.rb │ │ └── object.rb │ ├── rake.rb │ ├── rake │ └── suggest.rb │ ├── utils │ ├── buffered_ui.rb │ ├── code_location.rb │ ├── read_write_methods.rb │ ├── shell_helper.rb │ ├── ui.rb │ └── weighted_list.rb │ └── version.rb └── test ├── fixtures ├── elixir │ └── inch_test │ │ └── all.json ├── javascript │ └── inch_test │ │ └── all.json └── ruby │ ├── alias_cycle │ └── lib │ │ └── alias.rb │ ├── code_examples │ └── lib │ │ └── foo.rb │ ├── diff1 │ └── lib │ │ └── diff1.rb │ ├── diff2 │ └── lib │ │ └── diff2.rb │ ├── inch-yml │ ├── .inch.yml │ └── foo │ │ ├── bar.rb │ │ └── vendor │ │ └── base.rb │ ├── parameters │ └── lib │ │ └── foo.rb │ ├── readme │ └── lib │ │ └── foo.rb │ ├── really_good │ └── lib │ │ └── foo.rb │ ├── really_good_pedantic │ └── lib │ │ └── foo.rb │ ├── simple │ ├── README │ └── lib │ │ ├── broken.rb │ │ ├── broken_ruby_2_0_features.rb │ │ ├── directives.rb │ │ ├── foo.rb │ │ ├── nodoc.rb │ │ ├── role_methods.rb │ │ └── role_namespaces.rb │ ├── structs │ └── lib │ │ └── structs_member.rb │ ├── visibility │ └── lib │ │ └── foo.rb │ └── yardopts │ ├── .yardopts │ └── foo │ └── bar.rb ├── integration ├── api │ └── compare │ │ └── codebases.rb ├── cli │ └── command │ │ ├── console_test.rb │ │ ├── diff_test.rb │ │ ├── inspect_test.rb │ │ ├── list_test.rb │ │ ├── show_test.rb │ │ ├── stats_test.rb │ │ └── suggest_test.rb ├── stats_options_test.rb └── visibility_options_test.rb ├── shared └── base_list.rb ├── test_helper.rb └── unit ├── api ├── filter_test.rb ├── get_test.rb ├── list_test.rb ├── options │ └── base_test.rb ├── stats_test.rb └── suggest_test.rb ├── cli ├── arguments_test.rb ├── command │ ├── base_test.rb │ └── options │ │ ├── base_list_test.rb │ │ ├── base_object_test.rb │ │ └── base_test.rb ├── command_parser_test.rb ├── trace_helper_test.rb └── yardopts_helper_test.rb ├── code_object ├── converter_test.rb ├── provider_test.rb └── proxy_test.rb ├── codebase ├── objects_test.rb └── proxy_test.rb ├── config └── codebase_test.rb ├── config_test.rb ├── evaluation └── role_test.rb ├── language ├── elixir │ └── code_object │ │ ├── callback_object_test.rb │ │ ├── function_object_test.rb │ │ ├── macro_object_test.rb │ │ └── module_object_test.rb ├── javascript │ ├── code_object │ │ └── function_object_test.rb │ └── provider │ │ └── jsdoc │ │ └── docstring_test.rb └── ruby │ ├── code_object │ ├── alias_test.rb │ ├── method_object_test.rb │ └── structs_member_test.rb │ └── provider │ ├── yard │ ├── docstring_test.rb │ ├── nodoc_helper_test.rb │ └── object │ │ └── method_object_test.rb │ └── yard_test.rb └── utils ├── buffered_ui_test.rb ├── ui_test.rb └── weighted_list_test.rb /.github/workflows/ci-workflow.yml: -------------------------------------------------------------------------------- 1 | name: "CI Tests" 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - release/* 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | name: "[${{matrix.ruby}}/${{matrix.os}}] CI Tests [Ruby/OS]" 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ubuntu-20.04, macos-10.15, windows-2019] 19 | # 3.0 is interpreted as 3 20 | ruby: [2.2, 2.3, 2.4, 2.5, 2.6, 2.7, "3.0", 3.1, 3.2, 3.3] 21 | exclude: 22 | - { os: windows-2019, ruby: 2.2 } 23 | - { os: windows-2019, ruby: 2.3 } 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v3 27 | - name: Install Ruby & 'bundle install' 28 | uses: ruby/setup-ruby@v1 29 | with: 30 | ruby-version: ${{ matrix.ruby }} 31 | bundler-cache: true 32 | - name: Run Test 33 | run: | 34 | ruby -v 35 | bundle exec rake 36 | env: 37 | CI: true -------------------------------------------------------------------------------- /.github/workflows/smoke-test.yml: -------------------------------------------------------------------------------- 1 | name: "CI Tests" 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - release/* 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | name: "[${{matrix.ruby}}/${{matrix.os}}] CI Tests [Ruby/OS]" 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ubuntu-20.04, macos-10.15, windows-2019] 19 | ruby: [2.6, 2.7, "3.0", 3.1, 3.2, 3.3] 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | - name: Install Ruby & 'bundle install' 24 | uses: ruby/setup-ruby@v1 25 | with: 26 | ruby-version: ${{ matrix.ruby }} 27 | bundler-cache: true 28 | - name: Run Test 29 | run: | 30 | bundle install 31 | bundle exec inch 32 | env: 33 | CI: true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .inch 6 | .yardoc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | Include: 5 | - '**/*.gemspec' 6 | - '**/Gemfile' 7 | - '**/Rakefile' 8 | Exclude: 9 | - 'test/fixtures/**/*' 10 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | inch 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.1.6 2 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | SimpleCov.start do 2 | add_filter '/test/' 3 | 4 | add_group 'CLI', 'lib/inch/cli' 5 | add_group 'Code Objects', 'lib/inch/code_object' 6 | add_group 'Evaluation', 'lib/inch/evaluation' 7 | end 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 1.9.3 5 | - 2.0 6 | - 2.1 7 | - 2.2 8 | - 2.3 9 | - 2.4 10 | - ruby-head 11 | - jruby-head 12 | - jruby-19mode 13 | - rbx-2.2.3 14 | matrix: 15 | allow_failures: 16 | - rvm: ruby-head 17 | - rvm: jruby-head 18 | - rvm: jruby-19mode 19 | - rvm: rbx-2.2.3 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in inch.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 René Föhring 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | require 'rake/testtask' 4 | 5 | Rake::TestTask.new do |t| 6 | t.pattern = 'test/**/*_test.rb' 7 | t.warning = false 8 | end 9 | 10 | Rake::TestTask.new(:"test:unit") do |t| 11 | t.pattern = 'test/unit/**/*_test.rb' 12 | t.warning = false 13 | end 14 | 15 | Rake::TestTask.new(:"test:elixir") do |t| 16 | t.pattern = 'test/unit/language/elixir/**/*_test.rb' 17 | t.warning = false 18 | end 19 | 20 | Rake::TestTask.new(:"test:ruby") do |t| 21 | t.pattern = 'test/unit/language/ruby/**/*_test.rb' 22 | t.warning = false 23 | end 24 | 25 | Rake::TestTask.new(:"test:integration") do |t| 26 | t.pattern = 'test/integration/**/*_test.rb' 27 | t.warning = false 28 | end 29 | 30 | task default: :test 31 | -------------------------------------------------------------------------------- /TODOS.md: -------------------------------------------------------------------------------- 1 | # TODOs 2 | 3 | * Recognize all relevant options in .yardopts file 4 | * --plugin 5 | * --[no-]api API 6 | * Add support for multiple signatures for methods 7 | (realized via the @overload tag in YARD) 8 | 9 | * Add an option to suppress :nodoc: from output 10 | * Add a role for TaggedAsDeprecated 11 | -------------------------------------------------------------------------------- /bin/inch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # hit Control + C to stop 4 | Signal.trap('INT') do 5 | warn ' cancelled by user (INT)' 6 | exit 1 7 | end 8 | 9 | # @return [String] the path to the 'lib' directory of Inch 10 | def find_lib_path 11 | path = __FILE__ 12 | while File.symlink?(path) 13 | path = File.expand_path(File.readlink(path), File.dirname(path)) 14 | end 15 | File.join(File.dirname(File.expand_path(path)), '..', 'lib') 16 | end 17 | 18 | $LOAD_PATH.unshift(find_lib_path) 19 | 20 | require 'inch' 21 | require 'inch/cli' 22 | 23 | command = Inch::CLI::CommandParser.run(*ARGV) 24 | exit(command.exit_status) 25 | -------------------------------------------------------------------------------- /config/.inch.yml.sample: -------------------------------------------------------------------------------- 1 | language: ruby 2 | files: 3 | included: 4 | - lib/**/*.rb 5 | - app/**/*.rb 6 | excluded: 7 | - lib/vendor/**/*.rb 8 | -------------------------------------------------------------------------------- /config/base.rb: -------------------------------------------------------------------------------- 1 | Inch::Config.base do 2 | evaluation do 3 | grade(:A) do 4 | scores 80..100 5 | label 'Seems really good' 6 | color :green 7 | end 8 | 9 | grade(:B) do 10 | scores 50...80 11 | label 'Proper documentation present' 12 | color :yellow 13 | end 14 | 15 | grade(:C) do 16 | scores 1...50 17 | label 'Needs work' 18 | color :red 19 | end 20 | 21 | grade(:U) do 22 | scores 0..0 23 | label 'Undocumented' 24 | color :color141 25 | bg_color :color105 26 | end 27 | 28 | priority(:N) do 29 | priorities 4..99 30 | arrow "\u2191" 31 | end 32 | 33 | priority(:NE) do 34 | priorities 2...4 35 | arrow "\u2197" 36 | end 37 | 38 | priority(:E) do 39 | priorities 0...2 40 | arrow "\u2192" 41 | end 42 | 43 | priority(:SE) do 44 | priorities (-2...0) 45 | arrow "\u2198" 46 | end 47 | 48 | priority(:S) do 49 | priorities (-99...-2) 50 | arrow "\u2193" 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /config/elixir.rb: -------------------------------------------------------------------------------- 1 | # Elixir's configuration 2 | 3 | Inch::Config.register(:elixir) do 4 | codebase do 5 | object_provider :Reader 6 | include_files [] 7 | exclude_files [] 8 | end 9 | 10 | evaluation do 11 | schema(:ModuleObject) do 12 | docstring 1.0 13 | 14 | # optional: 15 | code_example_single 0.1 16 | code_example_multi 0.2 17 | unconsidered_tag 0.2 18 | end 19 | 20 | schema(:FunctionObject) do 21 | docstring 0.5 22 | parameters 0.4 23 | return_type 0.1 24 | return_description 0.3 25 | 26 | if object.questioning_name? 27 | parameters parameters + return_type 28 | return_type 0.0 29 | end 30 | 31 | if !object.has_parameters? || object.setter? 32 | return_description docstring + parameters 33 | docstring docstring + parameters 34 | parameters 0.0 35 | end 36 | 37 | # optional: 38 | code_example_single 0.1 39 | code_example_multi 0.25 40 | unconsidered_tag 0.2 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /config/javascript.rb: -------------------------------------------------------------------------------- 1 | # JavaScript's configuration 2 | 3 | Inch::Config.register(:javascript) do 4 | codebase do 5 | object_provider :JSDoc 6 | include_files ['src/**/*.js'] 7 | exclude_files [] 8 | end 9 | 10 | evaluation do 11 | schema(:MemberObject) do 12 | docstring 1.0 13 | 14 | # optional: 15 | code_example_single 0.1 16 | code_example_multi 0.2 17 | unconsidered_tag 0.2 18 | end 19 | 20 | schema(:ModuleObject) do 21 | docstring 1.0 22 | 23 | # optional: 24 | code_example_single 0.1 25 | code_example_multi 0.2 26 | unconsidered_tag 0.2 27 | end 28 | 29 | schema(:ClassObject) do 30 | docstring 1.0 31 | 32 | # optional: 33 | code_example_single 0.1 34 | code_example_multi 0.2 35 | unconsidered_tag 0.2 36 | end 37 | 38 | schema(:FunctionObject) do 39 | docstring 0.5 40 | parameters 0.4 41 | return_type 0.1 42 | return_description 0.3 43 | 44 | if object.questioning_name? 45 | parameters parameters + return_type 46 | return_type 0.0 47 | end 48 | 49 | if !object.has_parameters? || object.setter? 50 | return_description docstring + parameters 51 | docstring docstring + parameters 52 | parameters 0.0 53 | end 54 | 55 | # optional: 56 | code_example_single 0.1 57 | code_example_multi 0.25 58 | unconsidered_tag 0.2 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /config/ruby.rb: -------------------------------------------------------------------------------- 1 | # Ruby's configuration 2 | 3 | Inch::Config.register(:ruby) do 4 | codebase do 5 | object_provider :YARD 6 | include_files ['lib/**/*.rb', 'app/**/*.rb'] 7 | exclude_files [] 8 | end 9 | 10 | evaluation do 11 | schema(:ConstantObject) do 12 | docstring 1.0 13 | 14 | # optional: 15 | unconsidered_tag 0.2 16 | end 17 | 18 | schema(:ClassVariableObject) do 19 | docstring 1.0 20 | 21 | # optional: 22 | unconsidered_tag 0.2 23 | end 24 | 25 | schema(:ClassObject) do 26 | docstring 1.0 27 | 28 | # optional: 29 | code_example_single 0.1 30 | code_example_multi 0.2 31 | unconsidered_tag 0.2 32 | end 33 | 34 | schema(:ModuleObject) do 35 | docstring 1.0 36 | 37 | # optional: 38 | code_example_single 0.1 39 | code_example_multi 0.2 40 | unconsidered_tag 0.2 41 | end 42 | 43 | schema(:MethodObject) do 44 | docstring 0.5 45 | parameters 0.4 46 | return_type 0.1 47 | return_description 0.3 48 | 49 | if object.constructor? || object.questioning_name? 50 | parameters parameters + return_type 51 | return_type 0.0 52 | end 53 | 54 | if object.constructor? 55 | return_description 0.0 56 | end 57 | 58 | if object.setter? 59 | return_description return_description + parameters 60 | 61 | if object.original_docstring == "" 62 | # we don't count parameters when the docstring is missing or implicit 63 | parameters 0.0 64 | else 65 | parameters parameters + return_description 66 | end 67 | end 68 | 69 | if !object.has_parameters? 70 | return_description docstring + parameters 71 | docstring docstring + parameters 72 | parameters 0.0 73 | end 74 | 75 | # optional: 76 | code_example_single 0.1 77 | code_example_multi 0.25 78 | unconsidered_tag 0.2 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /inch.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'inch/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'inch' 8 | spec.version = Inch::VERSION 9 | spec.authors = ['René Föhring'] 10 | spec.email = ['rf@bamaru.de'] 11 | spec.summary = 'Documentation measurement tool for Ruby' 12 | spec.description = 'Documentation measurement tool for Ruby, based on YARD.' 13 | spec.homepage = 'https://trivelop.de/inch/' 14 | spec.license = 'MIT' 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(/^(test|spec|features)\//) 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_development_dependency 'bundler', '>= 1.5' 22 | spec.add_development_dependency 'rake' 23 | spec.add_development_dependency 'minitest', '~> 5.2' 24 | 25 | spec.add_dependency 'pry' 26 | spec.add_dependency 'sparkr', '>= 0.2.0' 27 | spec.add_dependency 'term-ansicolor' 28 | spec.add_dependency 'yard', '~> 0.9.12' 29 | end 30 | -------------------------------------------------------------------------------- /lib/inch.rb: -------------------------------------------------------------------------------- 1 | require 'inch/version' 2 | 3 | module Inch 4 | end 5 | 6 | require 'forwardable' 7 | 8 | require 'inch/api' 9 | require 'inch/core_ext' 10 | require 'inch/codebase' 11 | require 'inch/code_object' 12 | require 'inch/evaluation' 13 | 14 | require 'inch/language' 15 | 16 | require 'inch/config' 17 | require File.join(File.dirname(__FILE__), '..', 'config', 'base.rb') 18 | require File.join(File.dirname(__FILE__), '..', 'config', 'ruby.rb') 19 | require File.join(File.dirname(__FILE__), '..', 'config', 'javascript.rb') 20 | require File.join(File.dirname(__FILE__), '..', 'config', 'elixir.rb') 21 | -------------------------------------------------------------------------------- /lib/inch/api.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | # The API module is the entry point for Inch's APIs 3 | # 4 | # APIs are kind of "use cases" that are utilized by the CLI classes to 5 | # actually "do things". 6 | # 7 | # Example: 8 | # 9 | # $ inch list lib/**/*.rb --private 10 | # 11 | # This basically calls something like this: 12 | # 13 | # codebase = Codebase::Proxy.new(Dir.pwd, ["lib/**/*.rb"], []) 14 | # options = {:visibility => [:public, :protected, :private]} 15 | # context = API::List.new(codebase, options) 16 | # context.objects # => Array 17 | # 18 | # The List API takes a Codebase::Proxy object and an options 19 | # hash or a class in API::Options and returns objects and grade_lists 20 | # matching that options. 21 | # 22 | module API 23 | end 24 | end 25 | 26 | require 'inch/api/options/base' 27 | require 'inch/api/options/filter' 28 | require 'inch/api/options/suggest' 29 | 30 | require 'inch/api/compare' 31 | require 'inch/api/filter' 32 | require 'inch/api/get' 33 | require 'inch/api/list' 34 | require 'inch/api/suggest' 35 | require 'inch/api/stats' 36 | require 'inch/api/diff' 37 | -------------------------------------------------------------------------------- /lib/inch/api/compare.rb: -------------------------------------------------------------------------------- 1 | require 'inch/api/compare/code_objects' 2 | require 'inch/api/compare/codebases' 3 | -------------------------------------------------------------------------------- /lib/inch/api/compare/code_objects.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module API 3 | module Compare 4 | class CodeObjects 5 | attr_reader :before, :after 6 | 7 | def initialize(object1, object2) 8 | @before, @after = object1, object2 9 | if @before.object_id == @after.object_id 10 | fail '@before and @after are identical ruby objects. this is bad.' 11 | end 12 | end 13 | 14 | def changed? 15 | present? && !unchanged? 16 | end 17 | 18 | def fullname 19 | (@before || @after).fullname 20 | end 21 | 22 | def grade 23 | @after.grade 24 | end 25 | 26 | def added? 27 | @before.nil? && !@after.nil? 28 | end 29 | 30 | def degraded? 31 | changed? && @before.score > @after.score 32 | end 33 | 34 | def improved? 35 | changed? && @before.score < @after.score 36 | end 37 | 38 | def present? 39 | @before && @after 40 | end 41 | 42 | def removed? 43 | !@before.nil? && @after.nil? 44 | end 45 | 46 | def unchanged? 47 | present? && @before.score == @after.score 48 | end 49 | 50 | def scores 51 | [@before.score, @after.score] 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/inch/api/compare/codebases.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module API 3 | module Compare 4 | class Codebases 5 | def initialize(codebase1, codebase2) 6 | @a, @b = codebase1, codebase2 7 | end 8 | 9 | def added_objects 10 | comparisons.select(&:added?) 11 | end 12 | 13 | def improved_objects 14 | comparisons.select(&:improved?) 15 | end 16 | 17 | def degraded_objects 18 | comparisons.select(&:degraded?) 19 | end 20 | 21 | def removed_objects 22 | comparisons.select(&:removed?) 23 | end 24 | 25 | def comparisons 26 | __objects_names.map do |fullname| 27 | object1 = @a.objects.find(fullname) 28 | object2 = @b.objects.find(fullname) 29 | Compare::CodeObjects.new(object1, object2) 30 | end 31 | end 32 | 33 | def find(fullname) 34 | comparisons.find do |comparison| 35 | comparison.fullname == fullname 36 | end 37 | end 38 | 39 | private 40 | 41 | def __objects_names 42 | fullnames = @a.objects.all.map(&:fullname) + 43 | @b.objects.all.map(&:fullname) 44 | fullnames.uniq 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/inch/api/filter.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module API 3 | # Filters a codebase's objects based on given options 4 | class Filter 5 | attr_reader :codebase 6 | attr_reader :objects 7 | 8 | def initialize(codebase, options) 9 | @codebase = codebase 10 | codebase.objects.filter! Options::Filter.new(options) 11 | @objects = codebase.objects.to_a 12 | end 13 | 14 | def grade_lists(_objects = objects) 15 | lists = Evaluation.new_grade_lists 16 | lists.each do |range| 17 | list = _objects.select { |o| range.scores.include?(o.score) } 18 | range.objects = Codebase::Objects.sort_by_priority(list) 19 | end 20 | lists 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/inch/api/get.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module API 3 | # Gets all objects matching the given +object_names+ 4 | class Get < Filter 5 | attr_reader :object 6 | 7 | def initialize(codebase, object_names) 8 | super(codebase, {}) 9 | @objects = find_objects_with_names(object_names) 10 | @object = objects.first 11 | end 12 | 13 | private 14 | 15 | # Returns all objects matching the given +object_names+ 16 | # 17 | # @param object_names [Array] 18 | # @return [Array] 19 | def find_objects_with_names(object_names) 20 | object_names.map do |object_name| 21 | if (object = codebase.objects.find(object_name)) 22 | object 23 | else 24 | codebase.objects.starting_with(object_name) 25 | end 26 | end.flatten 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/inch/api/list.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module API 3 | class List < Filter 4 | def initialize(codebase, options) 5 | super 6 | @options = options 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/inch/api/options/base.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module API 3 | module Options 4 | class Base 5 | class << self 6 | # Creates an attribute with an optional default value 7 | # 8 | # @param name [Symbol] the name of the attribute 9 | # @param default [nil] the default value of the attribute 10 | # @return [void] 11 | def attribute(name, default = nil) 12 | define_method(name) do 13 | instance_variable_get("@#{name}") || default 14 | end 15 | @attributes ||= {} 16 | @attributes[to_s] ||= [] 17 | @attributes[to_s] << name 18 | end 19 | 20 | def attribute_names 21 | @attributes ||= {} 22 | @attributes[to_s] ||= [] 23 | end 24 | end 25 | 26 | def initialize(options_or_hash) 27 | self.class.attribute_names.each do |name| 28 | read options_or_hash, name 29 | end 30 | end 31 | 32 | protected 33 | 34 | def read(options_or_hash, name) 35 | value = if options_or_hash.is_a?(Hash) 36 | options_or_hash[name] 37 | else 38 | options_or_hash.send(name) 39 | end 40 | instance_variable_set("@#{name}", value) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/inch/api/options/filter.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module API 3 | module Options 4 | class Filter < Base 5 | # This module is included here and in Command::Options::BaseList 6 | # to ensure the same default values for the command-line and library 7 | # interface 8 | module DefaultAttributeValues 9 | DEFAULT_VISIBILITY = [:public, :protected] 10 | end 11 | 12 | include DefaultAttributeValues 13 | 14 | attribute :visibility, DEFAULT_VISIBILITY 15 | attribute :namespaces 16 | attribute :undocumented 17 | attribute :depth 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/inch/api/options/suggest.rb: -------------------------------------------------------------------------------- 1 | require 'inch/evaluation/proxy' 2 | 3 | module Inch 4 | module API 5 | module Options 6 | class Suggest < Base 7 | # This module is included here and in Command::Options::Suggest 8 | # to ensure the same default values for the command-line and library 9 | # interface 10 | module DefaultAttributeValues 11 | DEFAULT_OBJECT_COUNT = 20 12 | DEFAULT_FILE_COUNT = 5 13 | DEFAULT_PROPER_GRADES = [:A, :B] 14 | DEFAULT_GRADES_TO_DISPLAY = [:B, :C, :U] 15 | DEFAULT_GRADE_WEIGHTS = [0.2, 0.4, 0.4] 16 | DEFAULT_OBJECT_MIN_PRIORITY = 0 17 | DEFAULT_OBJECT_MAX_SCORE = ::Inch::Evaluation::Proxy::MAX_SCORE 18 | end 19 | 20 | include DefaultAttributeValues 21 | 22 | attribute :object_count, DEFAULT_OBJECT_COUNT 23 | attribute :file_count, DEFAULT_FILE_COUNT 24 | attribute :proper_grades, DEFAULT_PROPER_GRADES 25 | attribute :grades_to_display, DEFAULT_GRADES_TO_DISPLAY 26 | attribute :grade_weights, DEFAULT_GRADE_WEIGHTS 27 | attribute :object_min_priority, DEFAULT_OBJECT_MIN_PRIORITY 28 | attribute :object_max_score, DEFAULT_OBJECT_MAX_SCORE 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/inch/api/stats.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module API 3 | class Stats < Filter 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/inch/cli.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | # The CLI module is tasked with the deconstruction of CLI calls 3 | # into API calls. 4 | # 5 | # @see Inch::API 6 | module CLI 7 | class << self 8 | # Returns the columns of the terminal window 9 | # (defaults to 80) 10 | # @param default [Fixnum] default value for columns 11 | # @return [Fixnum] 12 | def get_term_columns(default = 80) 13 | str = `stty size 2>&1` 14 | if str =~ /Invalid argument/ 15 | default 16 | else 17 | rows_cols = str.split(' ').map(&:to_i) 18 | cols = rows_cols[1] 19 | if cols == 0 20 | default 21 | else 22 | cols || default 23 | end 24 | end 25 | rescue 26 | default 27 | end 28 | end 29 | COLUMNS = get_term_columns 30 | end 31 | end 32 | 33 | require 'inch/cli/arguments' 34 | require 'inch/cli/sparkline_helper' 35 | require 'inch/cli/trace_helper' 36 | require 'inch/cli/yardopts_helper' 37 | 38 | require 'inch/cli/command' 39 | -------------------------------------------------------------------------------- /lib/inch/cli/arguments.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | # Arguments parses given command-line arguments into the categories 4 | # +files+, +object_names+, and +switches+. 5 | # 6 | # @example 7 | # 8 | # args = ["lib/*.rb", "README", "Foo", "Foo::Bar", "--color", "--all"] 9 | # arguments = ::Inch::CLI::Arguments.new(args) 10 | # 11 | # arguments.files # => ["lib/*.rb", "README"] 12 | # arguments.object_names # => ["Foo", "Foo::Bar"] 13 | # arguments.switches # => ["--color", "--all"] 14 | # 15 | class Arguments 16 | attr_reader :files, :object_names, :switches 17 | 18 | # @param args [Array] 19 | def initialize(args) 20 | @files = [] 21 | @object_names = [] 22 | @switches = [] 23 | parse(args) 24 | end 25 | 26 | private 27 | 28 | # @param args [Array] 29 | # @return [void] 30 | def parse(args) 31 | if (first_non_file = args.find_index { |e| !glob_or_file?(e) }) 32 | @files = args[0...first_non_file] 33 | rest = args[first_non_file..-1] 34 | if (first_switch = rest.find_index { |e| switch?(e) }) 35 | @object_names = rest[0...first_switch] 36 | @switches = rest[first_switch..-1] 37 | else 38 | # object_names only 39 | @object_names = rest 40 | end 41 | else 42 | # files only 43 | @files = args 44 | end 45 | end 46 | 47 | # Returns +true+ if a given String is a glob or a filename 48 | # 49 | # @example 50 | # 51 | # glob_or_file?("lib/*.rb") # => true 52 | # glob_or_file?("README") # => true 53 | # glob_or_file?("--help") # => false 54 | # 55 | # @param f [String] 56 | # @return [Boolean] 57 | def glob_or_file?(f) 58 | if f =~ /[\*\{]/ 59 | true 60 | else 61 | File.file?(f) || File.directory?(f) 62 | end 63 | end 64 | 65 | # Returns +true+ if a given String is an option switch 66 | # 67 | # @example 68 | # 69 | # switch?("--help") # => true 70 | # switch?("README") # => false 71 | # 72 | # @param f [String] 73 | # @return [Boolean] 74 | def switch?(f) 75 | f =~ /^\-/ 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/inch/cli/command.rb: -------------------------------------------------------------------------------- 1 | require 'inch/cli/command/base' 2 | require 'inch/cli/command/base_list' 3 | require 'inch/cli/command/base_object' 4 | 5 | require 'inch/cli/command/options/base' 6 | require 'inch/cli/command/options/base_list' 7 | require 'inch/cli/command/options/base_object' 8 | 9 | require 'inch/cli/command/output/base' 10 | 11 | require 'inch/cli/command_parser' 12 | 13 | require 'inch/cli/command/list' 14 | require 'inch/cli/command/show' 15 | require 'inch/cli/command/stats' 16 | require 'inch/cli/command/suggest' 17 | require 'inch/cli/command/console' 18 | require 'inch/cli/command/inspect' 19 | require 'inch/cli/command/diff' 20 | -------------------------------------------------------------------------------- /lib/inch/cli/command/base_list.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | # Base class for Command objects concerned with lists of objects 5 | # 6 | # Commands subclassing from this class are called with an optional list 7 | # of paths in the form: 8 | # 9 | # $ inch COMMAND [paths] [options] 10 | # 11 | # @abstract 12 | class BaseList < Base 13 | attr_accessor :objects 14 | 15 | # Prepares the list of objects and grade_lists, parsing arguments and 16 | # running the source parser. 17 | # 18 | # @param *args [Array] the list of arguments. 19 | # @return [void] 20 | def prepare_codebase(*args) 21 | @options.parse(args) 22 | @options.verify 23 | 24 | @codebase = ::Inch::Codebase.parse(Dir.pwd, to_config(@options)) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/inch/cli/command/base_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | # Base class for Command objects concerned with clearly specified 5 | # objects. 6 | # 7 | # Commands subclassing from this class are called with a list of object 8 | # names (most commonly only one) in the form: 9 | # 10 | # $ inch COMMAND [paths] OBJECT_NAME [, OBJECT_NAME2, ...] [options] 11 | # 12 | # @abstract 13 | class BaseObject < BaseList 14 | attr_accessor :object 15 | 16 | # Prepares the given objects, parsing arguments and 17 | # running the source parser. 18 | # 19 | # @param *args [Array] the list of arguments 20 | # @return [void] 21 | def prepare_objects(*args) 22 | prepare_codebase(*args) 23 | 24 | context = API::Get.new(codebase, @options.object_names) 25 | self.objects = context.objects 26 | self.object = context.object 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/inch/cli/command/console.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'inch/cli/command/options/console' 3 | require 'inch/cli/command/output/console' 4 | 5 | module Inch 6 | module CLI 7 | module Command 8 | class Console < BaseObject 9 | register_command_as :console 10 | 11 | def description 12 | 'Shows a console' 13 | end 14 | 15 | def usage 16 | 'Usage: inch console [paths] [OBJECT_NAME] [options]' 17 | end 18 | 19 | def run(*args) 20 | prepare_objects(*args) 21 | Output::Console.new(@options, @object, @objects, codebase) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/inch/cli/command/diff.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'inch/cli/command/options/diff' 3 | require 'inch/cli/command/output/diff' 4 | 5 | module Inch 6 | module CLI 7 | module Command 8 | class Diff < Base 9 | include Utils::ShellHelper 10 | 11 | EXIT_STATUS_FAILED = 1 12 | 13 | register_command_as :diff 14 | 15 | def description 16 | 'Shows a diff' 17 | end 18 | 19 | def exit_status 20 | @exit_status || super 21 | end 22 | 23 | def usage 24 | 'Usage: inch diff [REV..[REV]] [options]' 25 | end 26 | 27 | def run(*args) 28 | @options.parse(args) 29 | @options.verify 30 | 31 | before_rev, after_rev = revisions[0], revisions[1] 32 | diff = API::Diff.new(work_dir, to_config(@options), 33 | before_rev, after_rev) 34 | 35 | Output::Diff.new(@options, diff.comparer) 36 | 37 | @exit_status = EXIT_STATUS_FAILED if diff.failed? 38 | end 39 | 40 | private 41 | 42 | # @return [Array] the revisions passed in the command_line 43 | def revisions 44 | @revisions ||= @options.revisions.map do |rev| 45 | next unless rev 46 | git(work_dir, "rev-parse #{rev}").strip 47 | end 48 | end 49 | 50 | def work_dir 51 | Dir.pwd 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/inch/cli/command/inspect.rb: -------------------------------------------------------------------------------- 1 | require 'inch/cli/command/options/inspect' 2 | require 'inch/cli/command/output/inspect' 3 | 4 | module Inch 5 | module CLI 6 | module Command 7 | class Inspect < BaseObject 8 | register_command_as :inspect 9 | 10 | def description 11 | 'Inspects an object' 12 | end 13 | 14 | def usage 15 | 'Usage: inch inspect [paths] OBJECT_NAME [[OBJECT_NAME2] ...] ' \ 16 | '[options]' 17 | end 18 | 19 | def run(*args) 20 | prepare_objects(*args) 21 | Output::Inspect.new(@options, objects) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/inch/cli/command/list.rb: -------------------------------------------------------------------------------- 1 | require 'inch/cli/command/options/list' 2 | require 'inch/cli/command/output/list' 3 | 4 | module Inch 5 | module CLI 6 | module Command 7 | class List < BaseList 8 | register_command_as :list 9 | 10 | def description 11 | 'Lists all objects with their results' 12 | end 13 | 14 | def usage 15 | 'Usage: inch list [paths] [options]' 16 | end 17 | 18 | # Runs the commandline utility, parsing arguments and displaying a 19 | # list of objects 20 | # 21 | # @param [Array] args the list of arguments. 22 | # @return [void] 23 | def run(*args) 24 | prepare_codebase(*args) 25 | context = API::List.new(codebase, @options) 26 | Output::List.new(@options, context.objects, context.grade_lists) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/inch/cli/command/options/base_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | module Options 5 | class BaseObject < Base 6 | attribute :object_names, [] 7 | 8 | def parse(args) 9 | opts = OptionParser.new 10 | opts.banner = usage 11 | 12 | descriptions.each do |text| 13 | opts.separator ' ' + text 14 | end 15 | 16 | set_options(opts) 17 | parse_yardopts_options(opts, args) 18 | parse_options(opts, args) 19 | 20 | @object_names = parse_object_names(args) 21 | @paths = get_paths(args) 22 | end 23 | 24 | def set_options(opts) 25 | common_options(opts) 26 | 27 | yardopts_options(opts) 28 | end 29 | 30 | private 31 | 32 | def parse_object_names(args) 33 | arguments = Arguments.new(args) 34 | object_names = arguments.object_names 35 | object_names.each do |n| 36 | @yard_files.delete(n) 37 | args.delete(n) 38 | end 39 | object_names 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/inch/cli/command/options/console.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | module Options 5 | class Console < BaseObject 6 | def descriptions 7 | [ 8 | '', 9 | 'Provides a PRY based REPL to inspect objects.', 10 | '', 11 | 'Example: ' + 12 | '$ inch console lib/**/*.rb Foo::Bar#initialize'.color(:cyan), 13 | '', 14 | 'Shortcut commands on the prompt are:', 15 | '', 16 | 'all'.ljust(5) + ' returns all code objects', 17 | 'f'.ljust(5) + ' finds an object by its path', 18 | 'ff'.ljust(5) + ' finds all objects given a partial path', 19 | 'o'.ljust(5) + 20 | ' returns the code object for OBJECT_NAME (if present)' 21 | ] 22 | end 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/inch/cli/command/options/inspect.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | module Options 5 | class Inspect < BaseObject 6 | def descriptions 7 | [ 8 | '', 9 | 'Example: ' + 10 | '$ inch inspect lib/**/*.rb Foo::Bar#initialize'.color(:cyan), 11 | '', 12 | 'Shows one or more objects in detail.' 13 | ] 14 | end 15 | 16 | def verify 17 | if object_names.empty? 18 | kill # "Provide a name to an object to show it's evaluation." 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/inch/cli/command/options/list.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | module Options 5 | class List < BaseList 6 | attribute :numbers, false 7 | 8 | def descriptions 9 | [ 10 | '', 11 | 'Lists objects that can be improved regarding their ' \ 12 | 'documentation ordered by their grade.', 13 | '', 14 | 'Example: ' + '$ inch list lib/**/*.rb --all'.color(:cyan), 15 | '', 16 | description_hint_grades, 17 | description_hint_arrows 18 | ] 19 | end 20 | 21 | def list_options(opts) 22 | super 23 | opts.on('--numbers', 'Show numbers instead of grades and arrows') do 24 | @numbers = true 25 | end 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/inch/cli/command/options/show.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | module Options 5 | class Show < BaseObject 6 | def descriptions 7 | [ 8 | '', 9 | 'Example: ' + 10 | '$ inch show lib/**/*.rb Foo::Bar#initialize'.color(:cyan), 11 | '', 12 | 'Shows one or more objects in detail.', 13 | description_hint_grades, 14 | description_hint_arrows 15 | ] 16 | end 17 | 18 | def verify 19 | if object_names.empty? 20 | kill # "Provide a name to an object to show it's evaluation." 21 | end 22 | end 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/inch/cli/command/options/stats.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | module Options 5 | class Stats < BaseList 6 | FORMATS = %w(text json yaml) 7 | 8 | attribute :format, FORMATS.first 9 | 10 | def list_options(opts) 11 | super 12 | opts.on('-f', '--format [FORMAT]', FORMATS, 13 | 'Set output FORMAT') do |format| 14 | @format = format 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/inch/cli/command/output/base.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | module Inch 4 | module CLI 5 | module Command 6 | # The classes in the Command::Output namespace act as presenter 7 | # objects to the classes in the Command namespace. 8 | # 9 | # They are given all the objects and data they are supposed 10 | # to display to the user. They do not filter the received data. 11 | # 12 | # @see Inch::CLI::Command::Suggest 13 | # @see Inch::CLI::Command::Output::Suggest 14 | module Output 15 | # Abstract base class for CLI output 16 | # 17 | # @abstract 18 | class Base 19 | include TraceHelper 20 | 21 | def display_name(object) 22 | if object.language == :javascript 23 | object.fullname+" in #{object.filename.relative_path}".color(:dark) 24 | else 25 | object.fullname 26 | end 27 | end 28 | 29 | def priority_arrow(priority, color = :white) 30 | Evaluation::PriorityRange.all.each do |range| 31 | return range.arrow.color(color).color(:dark) if range.include?(priority) 32 | end 33 | end 34 | 35 | def print_file_info(o, color) 36 | o.files.each do |f| 37 | echo "-> #{f.filename}:#{f.line_no}".color(color) 38 | end 39 | echo separator 40 | end 41 | 42 | # this is used to use Inch::Utils::BufferedIO 43 | def ui 44 | @options.ui 45 | end 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/inch/cli/command/output/console.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | module Output 5 | class Console < Base 6 | extend Forwardable 7 | 8 | attr_reader :object, :objects, :codebase 9 | 10 | COLOR = :color198 # magenta-ish 11 | BG_COLOR = :color207 # magenta-ish 12 | 13 | # @param options [Options::Console] 14 | # @param object [CodeObject::Proxy] 15 | # @param objects [Array] 16 | # @param codebase [Codebase::Proxy] 17 | def initialize(options, object, objects, codebase) 18 | @options = options 19 | @object = object 20 | @objects = objects 21 | @codebase = codebase 22 | 23 | run 24 | end 25 | 26 | def all_objects 27 | @codebase.objects.all 28 | end 29 | 30 | def find_objects(fullname) 31 | @codebase.objects.starting_with(fullname) 32 | end 33 | 34 | def find_object(fullname) 35 | @codebase.objects.find(fullname) 36 | end 37 | 38 | alias_method :all, :all_objects 39 | alias_method :ff, :find_objects 40 | alias_method :f, :find_object 41 | alias_method :o, :object 42 | 43 | def run 44 | ui.trace 45 | ui.header("Welcome to Inch's console", COLOR, BG_COLOR) 46 | ui.sub @options.usage 47 | @options.descriptions.each do |line| 48 | ui.sub line 49 | end 50 | run_pry 51 | end 52 | 53 | def run_pry 54 | binding.pry 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/inch/cli/command/output/show.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module Command 4 | module Output 5 | class Show < Base 6 | attr_reader :objects 7 | 8 | COLOR = :color132 9 | BG_COLOR = :color138 10 | LJUST = 20 11 | 12 | def initialize(options, objects) 13 | @options = options 14 | @objects = objects 15 | 16 | display_objects 17 | end 18 | 19 | private 20 | 21 | def display_objects 22 | objects.each do |o| 23 | print_object(o) 24 | end 25 | end 26 | 27 | def print_object(o) 28 | ui.trace 29 | ui.header(o.fullname, COLOR, BG_COLOR) 30 | 31 | print_file_info(o, COLOR) 32 | print_grade_info(o) 33 | print_roles_info(o) 34 | end 35 | 36 | def print_grade_info(o) 37 | echo "Grade: #{grade(o.score)}".rjust(5) 38 | echo separator 39 | end 40 | 41 | def print_roles_info(o) 42 | if o.roles.empty? 43 | echo 'No roles assigned.'.color(:dark) 44 | else 45 | o.roles.each do |role| 46 | next unless role.suggestion 47 | echo '+'.color(COLOR) + " #{role.suggestion}" 48 | end 49 | end 50 | echo separator 51 | end 52 | 53 | def echo(msg = '') 54 | ui.edged(COLOR, msg) 55 | end 56 | 57 | def separator 58 | '-'.color(COLOR) * (CLI::COLUMNS - 2) 59 | end 60 | 61 | def grade(score) 62 | grade_lists ||= Evaluation.new_grade_lists 63 | r = grade_lists.find { |v| v.scores.include?(score) } 64 | "#{r.grade} - #{r.label}".color(r.color) 65 | end 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/inch/cli/command/show.rb: -------------------------------------------------------------------------------- 1 | require 'inch/cli/command/options/show' 2 | require 'inch/cli/command/output/show' 3 | 4 | module Inch 5 | module CLI 6 | module Command 7 | class Show < BaseObject 8 | register_command_as :show 9 | 10 | def description 11 | 'Shows an object with its results' 12 | end 13 | 14 | def usage 15 | 'Usage: inch show [paths] OBJECT_NAME [[OBJECT_NAME2] ...] [options]' 16 | end 17 | 18 | def run(*args) 19 | prepare_objects(*args) 20 | Output::Show.new(@options, objects) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/inch/cli/command/stats.rb: -------------------------------------------------------------------------------- 1 | require 'inch/cli/command/options/stats' 2 | require 'inch/cli/command/output/stats' 3 | 4 | module Inch 5 | module CLI 6 | module Command 7 | class Stats < List 8 | register_command_as :stats 9 | 10 | def description 11 | 'Show statistics' 12 | end 13 | 14 | def usage 15 | 'Usage: inch stats [paths] [options]' 16 | end 17 | 18 | def run(*args) 19 | prepare_codebase(*args) 20 | context = API::Stats.new(codebase, @options) 21 | Output::Stats.new(@options, context.objects, context.grade_lists) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/inch/cli/command/suggest.rb: -------------------------------------------------------------------------------- 1 | require 'inch/cli/command/options/suggest' 2 | require 'inch/cli/command/output/suggest' 3 | 4 | module Inch 5 | module CLI 6 | module Command 7 | class Suggest < List 8 | register_command_as :suggest, true 9 | 10 | EXIT_CODE_PENDING_SUGGESTIONS = 10 11 | 12 | def description 13 | 'Suggests some objects to be documented (better)' 14 | end 15 | 16 | def usage 17 | 'Usage: inch suggest [paths] [options]' 18 | end 19 | 20 | # Runs the commandline utility, parsing arguments and displaying a 21 | # list of objects 22 | # 23 | # @param [Array] args the list of arguments. 24 | # @return [void] 25 | def run(*args) 26 | prepare_codebase(*args) 27 | context = API::Suggest.new(codebase, @options) 28 | self.objects = context.objects 29 | if @options.format == Options::Suggest::FORMAT_TEXT 30 | Output::Suggest.new(@options, context.all_objects, objects, 31 | context.grade_lists, context.files) 32 | else 33 | Output::Stats.new(@options, context.all_objects, context.grade_lists) 34 | end 35 | end 36 | 37 | # @return [Fixnum] 10 if suggestions were found, zero otherwise 38 | def exit_status 39 | objects.empty? ? super : EXIT_CODE_PENDING_SUGGESTIONS 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/inch/cli/sparkline_helper.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module SparklineHelper 4 | def grade_lists_sparkline(grade_lists) 5 | new_grade_lists = grade_lists.reverse 6 | list = new_grade_lists.map { |r| r.objects.size } 7 | __sparkline(list, new_grade_lists) 8 | end 9 | 10 | def grades_sparkline(objects) 11 | grades = {} 12 | objects.each do |o| 13 | grades[o.grade.to_sym] ||= 0 14 | grades[o.grade.to_sym] += 1 15 | end 16 | grade_lists = Evaluation.new_grade_lists.reverse 17 | order = grade_lists.map(&:to_sym) 18 | list = order.map { |g| grades[g] } 19 | __sparkline(list, grade_lists) 20 | end 21 | 22 | def __sparkline(list, grade_lists) 23 | sparkline = Sparkr::Sparkline.new(list) 24 | sparkline.format do |tick, _count, index| 25 | t = tick.color(grade_lists[index].color) 26 | index == 0 ? t + ' ' : t 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/inch/cli/trace_helper.rb: -------------------------------------------------------------------------------- 1 | require 'inch/utils/ui' 2 | 3 | module Inch 4 | module CLI 5 | # Adds a method called +ui+, that can be used to output messages to the 6 | # user. 7 | module TraceHelper 8 | def ui 9 | @ui ||= Inch::Utils::UI.new 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/inch/cli/yardopts_helper.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CLI 3 | module YardoptsHelper 4 | # @return [Array] list of Ruby source files to process 5 | attr_accessor :yard_files 6 | 7 | # @return [Array] list of excluded paths (regexp matches) 8 | attr_accessor :excluded 9 | 10 | VALID_YARD_SWITCHES = %w(--private --no-private --protected --no-public 11 | --plugin --load --safe --yardopts --no-yardopts 12 | --document --no-document) 13 | 14 | # Parses the option and gracefully handles invalid switches 15 | # 16 | # @param [OptionParser] opts the option parser object 17 | # @param [Array] args the arguments passed from input. This 18 | # array will be modified. 19 | # @return [void] 20 | def parse_yardopts_options(_opts, args) 21 | wrapper = YardoptsWrapper.new 22 | 23 | dupped_args = args.dup 24 | dupped_args.delete('--help') 25 | dupped_args.delete_if do |arg| 26 | arg =~ /^\-/ && !VALID_YARD_SWITCHES.include?(arg) 27 | end 28 | 29 | if ui 30 | ui.debug "Sending args to YARD:\n" \ 31 | " args: #{dupped_args}" 32 | end 33 | 34 | wrapper.parse_arguments(*dupped_args) 35 | 36 | self.yard_files = wrapper.files 37 | self.excluded = wrapper.excluded 38 | end 39 | 40 | def yardopts_options(opts) 41 | wrapper = YardoptsWrapper.new 42 | wrapper.add_yardoc_options(opts) 43 | end 44 | 45 | class YardoptsWrapper < YARD::CLI::Yardoc 46 | def add_yardoc_options(opts) 47 | yardopts_options(opts) 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/inch/code_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CodeObject 3 | end 4 | end 5 | 6 | require 'inch/code_object/converter' 7 | require 'inch/code_object/provider' 8 | require 'inch/code_object/proxy' 9 | -------------------------------------------------------------------------------- /lib/inch/code_object/provider.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CodeObject 3 | # Provider modules "provide" a Codebase object with code objects. 4 | # They are the intermediary between the raw representation that tools 5 | # like YARD deliver and the "interface" that Inch expects. 6 | # 7 | # YARD Example: 8 | # 9 | # YARD's SourceParser returns ::YARD::CodeObject objects, which are 10 | # cast to Provider::YARD::Object::Base objects that can ensure naming 11 | # conventions et al. follow certain rules. These objects are then again 12 | # converted into CodeObject::Proxy objects that form the codebase: 13 | # 14 | # ::YARD::CodeObject 15 | # | 16 | # ::Inch::CodeObject::Provider::YARD::Object::Base 17 | # | 18 | # (Hash) 19 | # | 20 | # ::Inch::CodeObject::Proxy 21 | # 22 | # 23 | module Provider 24 | # Parses a codebase to provide objects 25 | # 26 | # @param dir [String] the directory to parse 27 | # @param config [Inch::Config::Codebase] 28 | # @return [#objects] 29 | def self.parse(dir, config = Inch::Config.codebase) 30 | Config.namespace(config.language, :Provider) 31 | .const_get(config.object_provider) 32 | .parse(dir, config) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/inch/code_object/proxy.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module CodeObject 3 | # CodeObject::Proxy object represent code objects in the analaysed 4 | # codebase. 5 | # 6 | class Proxy 7 | # Returns a Proxy object for the given +code_object+ 8 | # 9 | # @param language [String,Symbol] 10 | # @param code_object [YARD::Object::Base] 11 | # @param object_lookup [Codebase::Objects] 12 | # @return [CodeObject::Proxy] 13 | def self.for(language, code_object, object_lookup) 14 | attributes = Converter.to_hash(code_object) 15 | class_for(language, code_object).new(attributes, object_lookup) 16 | end 17 | 18 | extend Forwardable 19 | 20 | # @return [#find] 21 | # an object that responds to #find to look up objects by their 22 | # full name 23 | attr_accessor :object_lookup 24 | 25 | # @param object_lookup [Codebase::Objects] 26 | def initialize(attributes = {}, object_lookup = nil) 27 | @attributes = attributes 28 | @object_lookup = object_lookup 29 | end 30 | 31 | # Returns the attribute for the given +key+ 32 | # 33 | # @param key [Symbol] 34 | def [](key) 35 | @attributes[key] 36 | end 37 | 38 | # @return [Symbol] the programming language of the code object 39 | def language 40 | fail NotImplementedError 41 | end 42 | 43 | # Used to persist the code object 44 | def marshal_dump 45 | @attributes 46 | end 47 | 48 | # Used to load a persisted code object 49 | def marshal_load(attributes) 50 | @attributes = attributes 51 | end 52 | 53 | def inspect 54 | "#<#{self.class}: #{fullname}>" 55 | end 56 | 57 | # Returns a Proxy class for the given +code_object+ 58 | # 59 | # @param language [String,Symbol] 60 | # @param code_object [YARD::CodeObject] 61 | # @return [Class] 62 | def self.class_for(language, code_object) 63 | class_name = code_object.class.to_s.split('::').last 64 | Config.namespace(language, :CodeObject).const_get(class_name) 65 | end 66 | private_class_method :class_for 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/inch/codebase.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | # Codebases are one of the building blocks of Inch's analysis (the other 3 | # being "code objects" inside these "codebases"). 4 | module Codebase 5 | # Parses a codebase 6 | # 7 | # @param dir [String] 8 | # @param config [Inch::Config::Codebase] 9 | # @return [Codebase::Proxy] 10 | def self.parse(dir, config) 11 | config.update_via_yaml(dir) 12 | Proxy.parse(dir, config) 13 | end 14 | end 15 | end 16 | 17 | require 'inch/codebase/proxy' 18 | require 'inch/codebase/object' 19 | require 'inch/codebase/objects' 20 | require 'inch/codebase/objects_filter' 21 | require 'inch/codebase/serializer' 22 | -------------------------------------------------------------------------------- /lib/inch/codebase/object.rb: -------------------------------------------------------------------------------- 1 | require 'inch/code_object/converter' 2 | 3 | module Inch 4 | module Codebase 5 | # An object that holds a code_object as well as it's evaluation 6 | # and exposes shortcut methods to the commonly asked members of 7 | # both. 8 | class Object 9 | extend Forwardable 10 | 11 | attr_reader :code_object 12 | 13 | # @return [String] 14 | attr_reader :language 15 | 16 | # @return [Grade] 17 | # when objects are assigned to GradeLists, this grade is set to 18 | # enable easier querying for objects of a certain grade 19 | attr_writer :grade 20 | 21 | # convenient shortcuts to evalution object 22 | def_delegators :evaluation, :score, :roles, :priority 23 | 24 | # convenient shortcuts to code object 25 | def_delegators :code_object, :object_lookup= 26 | 27 | # @param language [String,Symbol] 28 | # @param code_object [YARD::Object::Base] 29 | # @param object_lookup [Codebase::Objects] 30 | def initialize(language, code_object, object_lookup) 31 | @language = language 32 | @code_object = CodeObject::Proxy.for(language, code_object, 33 | object_lookup) 34 | end 35 | 36 | def evaluation 37 | @evaluation ||= Evaluation::Proxy.for(@language, self) 38 | end 39 | 40 | # @return [Grade] 41 | def grade 42 | @grade ||= Evaluation.new_grade_lists.find do |range| 43 | range.scores.include?(score) 44 | end.grade 45 | end 46 | 47 | def method_missing(name, *args, &block) 48 | if code_object.respond_to?(name) 49 | self.class.class_eval <<-RUBY 50 | def #{name}(*args, &block) 51 | code_object.#{name}(*args, &block) 52 | end 53 | RUBY 54 | code_object.send(name, *args, &block) 55 | else 56 | super 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/inch/codebase/objects_filter.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Codebase 3 | # ObjectsFilter can be used to filter a list of objects by a given set of 4 | # given +options+ 5 | class ObjectsFilter 6 | # @return [API::Options::Base] the filter options 7 | attr_reader :options 8 | 9 | # @param list [Array] the unfiltered list 10 | # @param options [API::Options::Base] the filter options 11 | def initialize(list, options) 12 | @list = list 13 | @options = options 14 | filter 15 | end 16 | 17 | # @return [Array] the filtered list 18 | def objects 19 | @list 20 | end 21 | 22 | private 23 | 24 | def filter 25 | filter_namespaces 26 | filter_undocumented 27 | filter_depth 28 | filter_visibility 29 | end 30 | 31 | def filter_namespaces 32 | if options.namespaces == :only 33 | @list = @list.select(&:namespace?) 34 | elsif options.namespaces == :none 35 | @list = @list.reject(&:namespace?) 36 | end 37 | end 38 | 39 | def filter_undocumented 40 | if options.undocumented == :only 41 | @list = @list.select(&:undocumented?) 42 | elsif options.undocumented == :none 43 | @list = @list.reject(&:undocumented?) 44 | end 45 | end 46 | 47 | def filter_depth 48 | @list = @list.select { |o| o.depth <= options.depth } if options.depth 49 | end 50 | 51 | def filter_visibility 52 | @list = @list.select do |o| 53 | options.visibility.include?(o.visibility) 54 | end 55 | unless options.visibility.include?(:private) 56 | @list = @list.reject do |o| 57 | o.tagged_as_private? 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/inch/codebase/proxy.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Codebase 3 | class Proxy 4 | attr_reader :objects 5 | 6 | def initialize(language, provider) 7 | @objects = Codebase::Objects.new(language, provider.objects) 8 | end 9 | 10 | def self.parse(dir, config) 11 | provider = CodeObject::Provider.parse(dir, config) 12 | new(config.language, provider) 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/inch/codebase/serializer.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Codebase 3 | class Serializer 4 | INCH_DB_DIR = File.join('.inch', 'db') 5 | 6 | def self.filename(revision) 7 | File.join(INCH_DB_DIR, revision) 8 | end 9 | 10 | def self.save(codebase, filename) 11 | content = Marshal.dump(codebase) 12 | FileUtils.mkdir_p(File.dirname(filename)) 13 | File.open(filename, 'wb') { |file| file.write(content) } 14 | end 15 | 16 | def self.load(filename) 17 | codebase = Marshal.load(File.binread(filename)) 18 | codebase.objects.each do |object| 19 | object.object_lookup = codebase.objects 20 | end 21 | codebase 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/inch/config.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | # Stores the configuration for Inch 3 | # 4 | # @see config/base.rb 5 | class Config 6 | class << self 7 | def instance(language = :ruby) 8 | if (block = @blocks[language.to_s]) 9 | config = Config::Base.new(language) 10 | config = config.update(&block) 11 | config 12 | else 13 | fail "Language not registered: #{language}" 14 | end 15 | end 16 | 17 | # Registers a configuration block for a given language. 18 | # 19 | # @return [void] 20 | def register(language, &block) 21 | @blocks ||= {} 22 | @blocks[language.to_s] = block 23 | end 24 | 25 | def codebase(language = :ruby) 26 | instance(language).codebase 27 | end 28 | 29 | def base(&block) 30 | Config::Base.new(:__base__).update(&block) 31 | end 32 | 33 | # Returns the Config object for a given +language+. 34 | # Optionally parses a given +path+ for inch.yml 35 | # 36 | # @return [Config::Base] 37 | def for(language, path = nil) 38 | config = instance(language) 39 | config.codebase.update_via_yaml(path) if path 40 | config 41 | end 42 | 43 | def namespace(language, submodule = nil) 44 | const_name = ::Inch::Language.constants.detect do |name| 45 | name.to_s.downcase == language.to_s.downcase 46 | end 47 | const = ::Inch::Language.const_get(const_name) 48 | const = const.const_get(submodule) unless submodule.nil? 49 | const 50 | end 51 | end 52 | end 53 | end 54 | 55 | require 'inch/config/base' 56 | require 'inch/config/evaluation' 57 | require 'inch/config/codebase' 58 | -------------------------------------------------------------------------------- /lib/inch/config/base.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | # Stores the configuration for Inch 3 | # 4 | # @see config/defaults.rb 5 | class Config 6 | class Base 7 | attr_reader :language 8 | 9 | def initialize(language) 10 | @language = language.to_sym 11 | end 12 | 13 | def update(&block) 14 | instance_eval(&block) 15 | self 16 | end 17 | 18 | def codebase(&block) 19 | @codebase ||= Config::Codebase.new(@language) 20 | @codebase.update(&block) if block 21 | @codebase 22 | end 23 | 24 | def evaluation(&block) 25 | @evaluation ||= Config::Evaluation.new(@language) 26 | @evaluation.update(&block) if block 27 | @evaluation 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/inch/config/evaluation.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | class Config 3 | class Evaluation 4 | def initialize(language) 5 | @language = language 6 | @criteria_blocks = {} 7 | end 8 | 9 | def update(&block) 10 | instance_eval(&block) 11 | end 12 | 13 | def grade(symbol, &block) 14 | ::Inch::Evaluation::Grade.grade(symbol, &block) 15 | end 16 | 17 | def priority(symbol, &block) 18 | ::Inch::Evaluation::PriorityRange.priority_range(symbol, &block) 19 | end 20 | 21 | def schema(constant_name, &block) 22 | @criteria_blocks[constant_name.to_s] = Criteria.new(&block) 23 | end 24 | 25 | def criteria_for(constant_name) 26 | @criteria_blocks[constant_name.to_s] || 27 | raise("No criteria for #{constant_name}") 28 | end 29 | 30 | # An Criteria describes how important certain parts of the docs are 31 | # for the associated Object 32 | class Criteria 33 | extend Utils::ReadWriteMethods 34 | 35 | rw_methods %w( 36 | docstring 37 | parameters 38 | return_type 39 | return_description 40 | code_example_single 41 | code_example_multi 42 | unconsidered_tag 43 | ) 44 | 45 | attr_reader :object 46 | 47 | def initialize(&block) 48 | @block = block 49 | end 50 | 51 | def evaluate(object) 52 | @object = object 53 | instance_eval(&@block) 54 | # we are "deleting" the block/Proc here because it can't be 55 | # serialized by Marshal 56 | # TODO: find a nicer way to achieve this 57 | @block = nil 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/inch/core_ext.rb: -------------------------------------------------------------------------------- 1 | require 'inch/core_ext/string' 2 | -------------------------------------------------------------------------------- /lib/inch/core_ext/string.rb: -------------------------------------------------------------------------------- 1 | require 'term/ansicolor' 2 | 3 | module Inch 4 | module StringExt 5 | def color(color_name) 6 | Term::ANSIColor.color(color_name, self) 7 | end 8 | 9 | def on_color(color_name) 10 | Term::ANSIColor.on_color(color_name, self) 11 | end 12 | 13 | def uncolor 14 | Term::ANSIColor.uncolor(self) 15 | end 16 | end 17 | end 18 | 19 | String.send(:include, Inch::StringExt) 20 | -------------------------------------------------------------------------------- /lib/inch/evaluation.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | # The Evaluation module concerns itself with the evaluation of code objects 3 | # with regard to their inline code documentation 4 | module Evaluation 5 | def self.for(language, code_object) 6 | class_for(language, code_object).new(code_object) 7 | end 8 | 9 | private 10 | 11 | def self.class_for(_language, code_object) 12 | class_name = code_object.class.to_s.split('::').last 13 | language_namespace = Evaluation::Ruby 14 | language_namespace.const_get(class_name) 15 | end 16 | end 17 | end 18 | 19 | require 'inch/utils/read_write_methods' 20 | 21 | require 'inch/evaluation/file' 22 | require 'inch/evaluation/grade' 23 | require 'inch/evaluation/grade_list' 24 | require 'inch/evaluation/priority_range' 25 | 26 | require 'inch/evaluation/proxy' 27 | require 'inch/evaluation/role' 28 | -------------------------------------------------------------------------------- /lib/inch/evaluation/file.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Evaluation 3 | # Evaluation::File is used in the Suggest API/CLI to determine 4 | # the importance of files 5 | class File 6 | attr_accessor :filename, :objects 7 | 8 | def initialize(filename, objects) 9 | self.filename = filename 10 | self.objects = objects.select do |o| 11 | o.filename == filename 12 | end 13 | end 14 | 15 | # @note added to be compatible with code objects 16 | def fullname 17 | filename 18 | end 19 | 20 | # 21 | # grade, priority and score are not meant to be displayed in the CLI 22 | # they are just for internal evaluation purposes 23 | # 24 | 25 | def grade 26 | median(grades.sort_by(&:to_sym)) 27 | end 28 | 29 | def priority 30 | median(priorities.sort) 31 | end 32 | 33 | def score 34 | objects.select(&:undocumented?).size 35 | end 36 | 37 | private 38 | 39 | def grades 40 | objects.map(&:grade) 41 | end 42 | 43 | def priorities 44 | objects.map(&:priority) 45 | end 46 | 47 | def median(sorted_list) 48 | index = (sorted_list.size / 2).round 49 | sorted_list[index] 50 | end 51 | 52 | class << self 53 | def for(filename, objects) 54 | @cache ||= {} 55 | if (file = @cache[filename]) 56 | file 57 | else 58 | @cache[filename] = new(filename, objects) 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/inch/evaluation/grade.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Evaluation 3 | # Grades are human-friendly representations of scores. 4 | # 5 | class Grade 6 | extend Utils::ReadWriteMethods 7 | 8 | rw_methods %w(scores label color bg_color) 9 | 10 | def initialize(symbol) 11 | @symbol = symbol 12 | end 13 | 14 | # Updates the grade's configuration with the given block 15 | # 16 | # @param block [Proc] 17 | # @return [void] 18 | def update(&block) 19 | instance_eval(&block) 20 | end 21 | 22 | # @return [Symbol] the grade as a symbol (e.g. +:A+) 23 | def to_sym 24 | @symbol 25 | end 26 | 27 | # @return [String] the grade as a string (e.g. "A") 28 | def to_s 29 | @symbol.to_s 30 | end 31 | 32 | class << self 33 | attr_reader :grade_map 34 | 35 | def all 36 | @grade_map ||= {} 37 | @grade_map.values 38 | end 39 | 40 | def grade(symbol, &block) 41 | @grade_map ||= {} 42 | @grade_map[symbol] ||= Grade.new(symbol) 43 | @grade_map[symbol].update(&block) if block 44 | @grade_map[symbol] 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/inch/evaluation/grade_list.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Evaluation 3 | # These objects associate a grade with a group of objects 4 | # 5 | # @see .new_grade_lists 6 | class GradeList < Struct.new(:grade) 7 | extend Forwardable 8 | 9 | def_delegators :grade, 10 | :scores, :label, :color, :bg_color, :to_s, :to_sym 11 | 12 | # Returns code_objects that received a score with the defined +scores+ 13 | attr_reader :objects 14 | 15 | # Assigns code_objects that received a score with the defined +scores+ 16 | # 17 | # @param arr [Array] 18 | # @return [Array] 19 | def objects=(arr) 20 | arr.each { |o| o.grade = grade } 21 | @objects = arr 22 | end 23 | end 24 | 25 | # Returns newly instanciated grade range objects 26 | # 27 | # @return [Array] 28 | def self.new_grade_lists 29 | Evaluation::Grade.all.map { |g| GradeList.new(g) } 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/inch/evaluation/priority_range.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Evaluation 3 | # PriorityRange objects are used to associate a given 4 | # range of priorities with a symbol and an arrow. 5 | # 6 | class PriorityRange 7 | extend Utils::ReadWriteMethods 8 | extend Forwardable 9 | 10 | rw_methods %w(priorities arrow) 11 | 12 | def_delegators :priorities, :include?, :min, :max 13 | 14 | def initialize(symbol) 15 | @symbol = symbol 16 | end 17 | 18 | def update(&block) 19 | instance_eval(&block) 20 | end 21 | 22 | def to_sym 23 | @symbol 24 | end 25 | 26 | def to_s 27 | arrow 28 | end 29 | 30 | class << self 31 | attr_reader :priority_map 32 | 33 | def all 34 | @priority_map ||= {} 35 | @priority_map.values 36 | end 37 | 38 | def priority_range(symbol, &block) 39 | @priority_map ||= {} 40 | @priority_map[symbol] ||= PriorityRange.new(symbol) 41 | @priority_map[symbol].update(&block) if block 42 | @priority_map[symbol] 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/inch/language.rb: -------------------------------------------------------------------------------- 1 | require 'inch/language/ruby/import' 2 | require 'inch/language/elixir/import' 3 | require 'inch/language/javascript/import' 4 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/code_object/function_object.rb: -------------------------------------------------------------------------------- 1 | require 'inch/language/elixir/code_object/function_parameter_object' 2 | 3 | module Inch 4 | module Language 5 | module Elixir 6 | module CodeObject 7 | # Proxy class for functions 8 | class FunctionObject < Base 9 | def bang_name? 10 | self[:bang_name?] 11 | end 12 | 13 | def getter? 14 | self[:getter?] 15 | end 16 | 17 | def has_parameters? 18 | !parameters.empty? 19 | end 20 | 21 | MANY_PARAMETERS_THRESHOLD = 3 22 | def has_many_parameters? 23 | parameters.size > MANY_PARAMETERS_THRESHOLD 24 | end 25 | 26 | def has_many_lines? 27 | false 28 | end 29 | 30 | def parameter(name) 31 | parameters.find { |p| p.name == name.to_s } 32 | end 33 | 34 | def parameters 35 | @parameters ||= self[:parameters].map do |param_attr| 36 | FunctionParameterObject.new(param_attr) 37 | end 38 | end 39 | 40 | def overridden? 41 | self[:overridden?] 42 | end 43 | 44 | def overridden_method 45 | @overridden_method ||= 46 | object_lookup.find(self[:overridden_method_fullname]) 47 | end 48 | 49 | def return_mentioned? 50 | self[:return_mentioned?] 51 | end 52 | 53 | def return_described? 54 | self[:return_described?] 55 | end 56 | 57 | def return_typed? 58 | self[:return_typed?] 59 | end 60 | 61 | def setter? 62 | self[:setter?] 63 | end 64 | 65 | def source 66 | self[:source?] 67 | end 68 | 69 | def questioning_name? 70 | self[:questioning_name?] 71 | end 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/code_object/function_parameter_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module CodeObject 5 | # Proxy class for method parameters 6 | class FunctionParameterObject 7 | def initialize(attributes) 8 | @attributes = attributes 9 | end 10 | 11 | def [](key) 12 | @attributes[key] 13 | end 14 | 15 | IGNORE_NAME_PREFIX = "_" 16 | BAD_NAME_EXCEPTIONS = %w(id) 17 | BAD_NAME_THRESHOLD = 3 18 | 19 | # @return [Boolean] +true+ if the name of the parameter is 20 | # uncommunicative 21 | def bad_name? 22 | return false if BAD_NAME_EXCEPTIONS.include?(name) 23 | name.size < BAD_NAME_THRESHOLD || name =~ /[0-9]$/ 24 | end 25 | 26 | # @return [Boolean] +true+ if the parameter is a block 27 | def block? 28 | self[:block?] 29 | end 30 | 31 | # @return [Boolean] +true+ if an additional description given? 32 | def described? 33 | self[:described?] || ignore? 34 | end 35 | 36 | # @return [Boolean] +true+ if the parameter is mentioned in the docs 37 | def mentioned? 38 | self[:mentioned?] || ignore? 39 | end 40 | 41 | def name 42 | self[:name] 43 | end 44 | alias_method :fullname, :name 45 | 46 | # @return [Boolean] +true+ if the parameter is a splat argument 47 | def splat? 48 | self[:splat?] 49 | end 50 | 51 | # @return [Boolean] +true+ if the type of the parameter is defined 52 | def typed? 53 | self[:typed?] || ignore? 54 | end 55 | 56 | def unnamed? 57 | name == '' 58 | end 59 | 60 | # @return [Boolean] +true+ if the parameter is mentioned in the docs, 61 | # but not present in the method's signature 62 | def wrongly_mentioned? 63 | self[:wrongly_mentioned?] 64 | end 65 | 66 | private 67 | 68 | def ignore? 69 | name.start_with?(IGNORE_NAME_PREFIX) 70 | end 71 | end 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/code_object/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module CodeObject 5 | # Proxy class for modules 6 | class ModuleObject < Base 7 | MANY_CHILDREN_THRESHOLD = 20 8 | def has_many_children? 9 | children.size > MANY_CHILDREN_THRESHOLD 10 | end 11 | 12 | def has_methods? 13 | children.any?(&:method?) 14 | end 15 | 16 | def pure_namespace? 17 | children.all?(&:namespace?) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/code_object/type_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module CodeObject 5 | # Proxy class for types 6 | class TypeObject < Base 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/evaluation/base.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Evaluation 5 | # Base class for all Elixir related evaluations 6 | # 7 | # @abstract 8 | class Base < Inch::Evaluation::Proxy 9 | protected 10 | 11 | def relevant_base_roles 12 | { 13 | Role::Object::InRoot => nil, 14 | Role::Object::Public => nil, 15 | Role::Object::TaggedAsNodoc => nil, 16 | Role::Object::WithDoc => score_for(:docstring), 17 | Role::Object::WithoutDoc => score_for(:docstring), 18 | Role::Object::WithCodeExample => score_for(:code_example_single), 19 | Role::Object::WithMultipleCodeExamples => 20 | score_for(:code_example_multi), 21 | Role::Object::WithoutCodeExample => 22 | score_for(:code_example_single) 23 | } 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/evaluation/function_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Evaluation 5 | # Proxy class for functions 6 | class FunctionObject < Base 7 | def evaluate 8 | super 9 | evaluate_parameters 10 | end 11 | 12 | protected 13 | 14 | def relevant_roles 15 | relevant_base_roles.merge(relevant_function_roles) 16 | end 17 | 18 | private 19 | 20 | def evaluate_parameters 21 | params = object.parameters 22 | per_param = score_for_single_parameter 23 | params.each do |param| 24 | role_classes = relevant_parameter_roles(param, per_param) 25 | __evaluate(param, role_classes) 26 | end 27 | end 28 | 29 | def relevant_function_roles 30 | { 31 | Role::Function::Getter => nil, 32 | Role::Function::Setter => nil, 33 | Role::Function::Overridden => 34 | if object.overridden? 35 | object.overridden_method.score 36 | else 37 | nil 38 | end, 39 | Role::Function::WithBangName => nil, 40 | Role::Function::WithQuestioningName => nil 41 | } 42 | end 43 | 44 | def relevant_parameter_roles(_param, per_param) 45 | score_for_mention = per_param 46 | score_for_type = 0 47 | { 48 | Role::FunctionParameter::WithWrongMention => 49 | -score_for(:parameters), 50 | Role::FunctionParameter::WithMention => score_for_mention, 51 | Role::FunctionParameter::WithoutMention => score_for_mention, 52 | Role::FunctionParameter::WithType => score_for_type, 53 | Role::FunctionParameter::WithoutType => score_for_type, 54 | Role::FunctionParameter::WithBadName => nil, 55 | } 56 | end 57 | 58 | def score_for_single_parameter 59 | @param_score ||= score_for(:parameters) / object.parameters.size 60 | end 61 | 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/evaluation/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Evaluation 5 | # Proxy class for modules 6 | class ModuleObject < Base 7 | protected 8 | 9 | def relevant_roles 10 | relevant_base_roles.merge(relevant_namespace_roles) 11 | end 12 | 13 | # @see Evaluation::Ruby::Base 14 | def relevant_namespace_roles 15 | { 16 | Role::Module::WithoutChildren => nil, 17 | Role::Module::WithChildren => nil, 18 | Role::Module::WithManyChildren => nil, 19 | Role::Module::WithoutMethods => nil, 20 | Role::Module::Pure => nil 21 | } 22 | end 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/evaluation/type_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Evaluation 5 | # Proxy class for types 6 | class TypeObject < Base 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/import.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | end 5 | end 6 | end 7 | 8 | require 'inch/language/elixir/provider/reader' 9 | 10 | require 'inch/language/elixir/code_object/base' 11 | require 'inch/language/elixir/code_object/module_object' 12 | require 'inch/language/elixir/code_object/function_object' 13 | require 'inch/language/elixir/code_object/type_object' 14 | 15 | require 'inch/language/elixir/evaluation/base' 16 | require 'inch/language/elixir/evaluation/module_object' 17 | require 'inch/language/elixir/evaluation/function_object' 18 | require 'inch/language/elixir/evaluation/type_object' 19 | 20 | require 'inch/language/elixir/roles/base' 21 | require 'inch/language/elixir/roles/object' 22 | require 'inch/language/elixir/roles/module' 23 | require 'inch/language/elixir/roles/function' 24 | require 'inch/language/elixir/roles/function_parameter' 25 | require 'inch/language/elixir/roles/type' 26 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'inch/language/elixir/provider/parser' 3 | 4 | module Inch 5 | module Language 6 | module Elixir 7 | module Provider 8 | # Parses the source tree (using Reader) 9 | module Reader 10 | # @see Provider.parse 11 | def self.parse(dir, config) 12 | Parser.parse(dir, config) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader/docstring.rb: -------------------------------------------------------------------------------- 1 | 2 | module Inch 3 | module Language 4 | module Elixir 5 | module Provider 6 | module Reader 7 | class Docstring < Ruby::Provider::YARD::Docstring 8 | end 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader/object.rb: -------------------------------------------------------------------------------- 1 | require 'inch/language/elixir/provider/reader/object/base' 2 | require 'inch/language/elixir/provider/reader/object/function_object' 3 | require 'inch/language/elixir/provider/reader/object/module_object' 4 | require 'inch/language/elixir/provider/reader/object/type_object' 5 | 6 | module Inch 7 | module Language 8 | module Elixir 9 | module Provider 10 | module Reader 11 | # CodeObject::Provider::JSDoc::Object object represent code objects. 12 | # 13 | module Object 14 | class << self 15 | def clear_cache 16 | @cache = {} 17 | end 18 | 19 | # Returns a Proxy object for the given +json_object+ 20 | # 21 | # @param json_object [Hash] 22 | # @return [Provider::JSDoc::Object] 23 | def for(json_object) 24 | @cache ||= {} 25 | if (proxy_object = @cache[cache_key(json_object)]) 26 | proxy_object 27 | else 28 | @cache[cache_key(json_object)] = 29 | class_for(json_object).new(json_object) 30 | end 31 | end 32 | 33 | private 34 | 35 | # Returns a Proxy class for the given +json_object+ 36 | # 37 | # @param json_object [Hash] 38 | # @return [Class] 39 | def class_for(json_object) 40 | class_name = json_object['object_type'] 41 | Reader::Object.const_get(class_name) 42 | rescue NameError 43 | Reader::Object::Base 44 | end 45 | 46 | # Returns a cache key for the given +json_object+ 47 | # 48 | # @param json_object [Hash] 49 | # @return [String] 50 | def cache_key(json_object) 51 | [ 52 | json_object['id'], 53 | json_object['module_id'], 54 | json_object['signature'] 55 | ].map(&:to_s).join('.') 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader/object/function_object.rb: -------------------------------------------------------------------------------- 1 | require 'inch/language/elixir/provider/reader/object/function_parameter_object' 2 | 3 | module Inch 4 | module Language 5 | module Elixir 6 | module Provider 7 | module Reader 8 | module Object 9 | # Proxy class for functions 10 | class FunctionObject < Base 11 | def name 12 | @hash['id'] 13 | end 14 | 15 | def fullname 16 | @hash['module_id'] + '.' + @hash['id'] 17 | end 18 | 19 | def method? 20 | true 21 | end 22 | 23 | def parameters 24 | names = FunctionSignature.new(@hash['signature']).parameter_names 25 | names.map do |name| 26 | FunctionParameterObject.new(self, name) 27 | end 28 | end 29 | 30 | private 31 | 32 | class FunctionSignature < Struct.new(:signature) 33 | def parameter_names 34 | return [] if signature.nil? 35 | signature.map do |tuple| 36 | name_from_tuple(*tuple) 37 | end 38 | end 39 | 40 | def name_from_tuple(a, _, b) 41 | if b.nil? || b == 'Elixir' 42 | a 43 | else 44 | if a == '\\\\' 45 | candidate = b.first 46 | if candidate.is_a?(Array) 47 | name_from_tuple(*candidate) 48 | else 49 | candidate 50 | end 51 | else 52 | warn "[WARN] could not parse FunctionSignature: #{[a, _, b].inspect}" 53 | end 54 | end 55 | end 56 | end 57 | 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader/object/function_parameter_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Provider 5 | module Reader 6 | module Object 7 | # Proxy class for function parameters 8 | class FunctionParameterObject 9 | attr_reader :name # @return [String] 10 | 11 | # @param method [YARD::Object::MethodObject] the method the 12 | # parameter belongs to 13 | # @param name [String] the name of the parameter 14 | def initialize(method, name) 15 | @method = method 16 | @name = name 17 | end 18 | 19 | # @return [Boolean] +true+ if the parameter is a block 20 | def block? 21 | false 22 | end 23 | 24 | # @return [Boolean] +true+ if an additional description is given? 25 | def described? 26 | described_by_docstring? 27 | end 28 | 29 | # @return [Boolean] +true+ if the parameter is mentioned in the 30 | # docs 31 | def mentioned? 32 | mentioned_by_docstring? 33 | end 34 | 35 | # @return [Boolean] +true+ if the parameter is a splat argument 36 | def splat? 37 | false 38 | end 39 | 40 | # @return [Boolean] +true+ if the type of the parameter is defined 41 | def typed? 42 | false # TODO: parse types of params 43 | end 44 | 45 | # @return [Boolean] +true+ if the parameter is mentioned in the 46 | # docs, but not present in the method's signature 47 | def wrongly_mentioned? 48 | false 49 | end 50 | 51 | private 52 | 53 | def described_by_docstring? 54 | @method.docstring.describes_parameter?(name) 55 | end 56 | 57 | def mentioned_by_docstring? 58 | @method.docstring.mentions_parameter?(name) 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader/object/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Provider 5 | module Reader 6 | module Object 7 | # Proxy class for modules 8 | class ModuleObject < Base 9 | def original_docstring 10 | @hash['moduledoc'] 11 | end 12 | 13 | def nodoc? 14 | @hash['moduledoc'] == false || super 15 | end 16 | 17 | def fullname 18 | @hash['id'] 19 | end 20 | 21 | def namespace? 22 | true 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader/object/type_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Provider 5 | module Reader 6 | module Object 7 | # Proxy class for types 8 | class TypeObject < Base 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader_v2/docstring.rb: -------------------------------------------------------------------------------- 1 | 2 | module Inch 3 | module Language 4 | module Elixir 5 | module Provider 6 | module ReaderV2 7 | class Docstring < Ruby::Provider::YARD::Docstring 8 | end 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader_v2/object.rb: -------------------------------------------------------------------------------- 1 | require 'inch/language/elixir/provider/reader_v2/object/base' 2 | require 'inch/language/elixir/provider/reader_v2/object/function_object' 3 | require 'inch/language/elixir/provider/reader_v2/object/module_object' 4 | require 'inch/language/elixir/provider/reader_v2/object/type_object' 5 | 6 | module Inch 7 | module Language 8 | module Elixir 9 | module Provider 10 | module ReaderV2 11 | # CodeObject::Provider::JSDoc::Object object represent code objects. 12 | # 13 | module Object 14 | class << self 15 | def clear_cache 16 | @cache = {} 17 | end 18 | 19 | # Returns a Proxy object for the given +json_object+ 20 | # 21 | # @param json_object [Hash] 22 | # @return [Provider::JSDoc::Object] 23 | def for(json_object) 24 | @cache ||= {} 25 | 26 | if (proxy_object = @cache[cache_key(json_object)]) 27 | proxy_object 28 | else 29 | @cache[cache_key(json_object)] = 30 | class_for(json_object).new(json_object) 31 | end 32 | end 33 | 34 | private 35 | 36 | # Returns a Proxy class for the given +json_object+ 37 | # 38 | # @param json_object [Hash] 39 | # @return [Class] 40 | def class_for(json_object) 41 | class_name = json_object['type'].capitalize + "Object" 42 | 43 | ReaderV2::Object.const_get(class_name) 44 | rescue NameError 45 | ReaderV2::Object::Base 46 | end 47 | 48 | # Returns a cache key for the given +json_object+ 49 | # 50 | # @param json_object [Hash] 51 | # @return [String] 52 | def cache_key(json_object) 53 | json_object['name'] 54 | end 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader_v2/object/function_object.rb: -------------------------------------------------------------------------------- 1 | require 'inch/language/elixir/provider/reader_v2/object/function_parameter_object' 2 | 3 | module Inch 4 | module Language 5 | module Elixir 6 | module Provider 7 | module ReaderV2 8 | module Object 9 | # Proxy class for functions 10 | class FunctionObject < Base 11 | def method? 12 | true 13 | end 14 | 15 | def parameters 16 | names = FunctionSignature.new(@hash['signature']).parameter_names 17 | names.map do |name| 18 | FunctionParameterObject.new(self, name) 19 | end 20 | end 21 | 22 | private 23 | 24 | class FunctionSignature < Struct.new(:signature) 25 | def parameter_names 26 | return [] if signature.nil? 27 | signature.map do |tuple| 28 | name_from_tuple(*tuple) 29 | end 30 | end 31 | 32 | def name_from_tuple(a, _, b) 33 | if b.nil? || b == 'Elixir' 34 | a 35 | else 36 | if a == '\\\\' 37 | candidate = b.first 38 | if candidate.is_a?(Array) 39 | name_from_tuple(*candidate) 40 | else 41 | candidate 42 | end 43 | else 44 | warn "[WARN] could not parse FunctionSignature: #{[a, _, b].inspect}" 45 | end 46 | end 47 | end 48 | end 49 | 50 | end 51 | end 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader_v2/object/function_parameter_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Provider 5 | module ReaderV2 6 | module Object 7 | # Proxy class for function parameters 8 | class FunctionParameterObject 9 | attr_reader :name # @return [String] 10 | 11 | # @param method [YARD::Object::MethodObject] the method the 12 | # parameter belongs to 13 | # @param name [String] the name of the parameter 14 | def initialize(method, name) 15 | @method = method 16 | @name = name 17 | end 18 | 19 | # @return [Boolean] +true+ if the parameter is a block 20 | def block? 21 | false 22 | end 23 | 24 | # @return [Boolean] +true+ if an additional description is given? 25 | def described? 26 | described_by_docstring? 27 | end 28 | 29 | # @return [Boolean] +true+ if the parameter is mentioned in the 30 | # docs 31 | def mentioned? 32 | mentioned_by_docstring? 33 | end 34 | 35 | # @return [Boolean] +true+ if the parameter is a splat argument 36 | def splat? 37 | false 38 | end 39 | 40 | # @return [Boolean] +true+ if the type of the parameter is defined 41 | def typed? 42 | false # TODO: parse types of params 43 | end 44 | 45 | # @return [Boolean] +true+ if the parameter is mentioned in the 46 | # docs, but not present in the method's signature 47 | def wrongly_mentioned? 48 | false 49 | end 50 | 51 | private 52 | 53 | def described_by_docstring? 54 | @method.docstring.describes_parameter?(name) 55 | end 56 | 57 | def mentioned_by_docstring? 58 | @method.docstring.mentions_parameter?(name) 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader_v2/object/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Provider 5 | module ReaderV2 6 | module Object 7 | # Proxy class for modules 8 | class ModuleObject < Base 9 | def namespace? 10 | true 11 | end 12 | end 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/provider/reader_v2/object/type_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Provider 5 | module ReaderV2 6 | module Object 7 | # Proxy class for types 8 | class TypeObject < Base 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/roles/base.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Evaluation 5 | module Role 6 | # @abstract 7 | class Base < Inch::Evaluation::Role 8 | # Returns the type of the +object+ that is being evaluated. 9 | def object_type 10 | object.class.to_s.split('::').last.gsub(/Object$/, '').downcase 11 | end 12 | end 13 | 14 | # Missing is the base class for things that can be improved in the doc 15 | # 16 | class Missing < Base 17 | def score 18 | nil 19 | end 20 | 21 | # @return [Fixnum] 22 | # a score that can be achieved by adding the missing thing 23 | # mentioned by the role 24 | def potential_score 25 | @value.to_i 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/roles/module.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Evaluation 5 | module Role 6 | # Roles assigned to modules 7 | module Module 8 | # Role assigned to modules with children 9 | # 10 | # @see CodeObject::Ruby::NamespaceObject#has_children? 11 | class WithChildren < Base 12 | applicable_if :has_children? 13 | 14 | # This role doesnot assign a score. 15 | def score 16 | 0 17 | end 18 | 19 | # This role sets a max_score. 20 | def max_score 21 | # @value.to_f 22 | end 23 | end 24 | 25 | # Role assigned to modules with many children 26 | # 27 | # @see CodeObject::Ruby::NamespaceObject#has_many_children? 28 | class WithManyChildren < Base 29 | applicable_if :has_many_children? 30 | 31 | # +priority 32 | def priority 33 | +1 34 | end 35 | end 36 | 37 | # Role assigned to modules without any children 38 | class WithoutChildren < Base 39 | applicable_unless :has_children? 40 | end 41 | 42 | # Role assigned to modules without any methods 43 | class WithoutMethods < Base 44 | applicable_unless :has_methods? 45 | 46 | def priority 47 | -2 48 | end 49 | end 50 | 51 | # A 'pure' namespace has only modules as children 52 | class Pure < Base 53 | applicable_if :pure_namespace? 54 | 55 | def priority 56 | -2 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/inch/language/elixir/roles/type.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Elixir 4 | module Evaluation 5 | module Role 6 | # Roles assigned to types 7 | module Type 8 | end 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/code_object/class_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module CodeObject 5 | # Proxy class for modules 6 | class ClassObject < ModuleObject 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/code_object/function_object.rb: -------------------------------------------------------------------------------- 1 | require 'inch/language/javascript/code_object/function_parameter_object' 2 | 3 | module Inch 4 | module Language 5 | module JavaScript 6 | module CodeObject 7 | # Proxy class for functions 8 | class FunctionObject < Base 9 | def bang_name? 10 | self[:bang_name?] 11 | end 12 | 13 | def getter? 14 | self[:getter?] 15 | end 16 | 17 | def has_parameters? 18 | !parameters.empty? 19 | end 20 | 21 | MANY_PARAMETERS_THRESHOLD = 3 22 | def has_many_parameters? 23 | parameters.size > MANY_PARAMETERS_THRESHOLD 24 | end 25 | 26 | def has_many_lines? 27 | false 28 | end 29 | 30 | def parameter(name) 31 | parameters.find { |p| p.name == name.to_s } 32 | end 33 | 34 | def parameters 35 | @parameters ||= self[:parameters].map do |param_attr| 36 | FunctionParameterObject.new(param_attr) 37 | end 38 | end 39 | 40 | def overridden? 41 | self[:overridden?] 42 | end 43 | 44 | def overridden_method 45 | @overridden_method ||= 46 | object_lookup.find(self[:overridden_method_fullname]) 47 | end 48 | 49 | def return_mentioned? 50 | self[:return_mentioned?] 51 | end 52 | 53 | def return_described? 54 | self[:return_described?] 55 | end 56 | 57 | def return_typed? 58 | self[:return_typed?] 59 | end 60 | 61 | def setter? 62 | self[:setter?] 63 | end 64 | 65 | def source 66 | self[:source?] 67 | end 68 | 69 | def questioning_name? 70 | self[:questioning_name?] 71 | end 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/code_object/function_parameter_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module CodeObject 5 | # Proxy class for method parameters 6 | class FunctionParameterObject 7 | def initialize(attributes) 8 | @attributes = attributes 9 | end 10 | 11 | def [](key) 12 | @attributes[key] 13 | end 14 | 15 | BAD_NAME_EXCEPTIONS = %w(id) 16 | BAD_NAME_THRESHOLD = 3 17 | 18 | # @return [Boolean] +true+ if the name of the parameter is 19 | # uncommunicative 20 | def bad_name? 21 | return false if BAD_NAME_EXCEPTIONS.include?(name) 22 | name.size < BAD_NAME_THRESHOLD || name =~ /[0-9]$/ 23 | end 24 | 25 | # @return [Boolean] +true+ if the parameter is a block 26 | def block? 27 | self[:block?] 28 | end 29 | 30 | # @return [Boolean] +true+ if an additional description given? 31 | def described? 32 | self[:described?] 33 | end 34 | 35 | # @return [Boolean] +true+ if the parameter is mentioned in the docs 36 | def mentioned? 37 | self[:mentioned?] 38 | end 39 | 40 | def name 41 | self[:name] 42 | end 43 | alias_method :fullname, :name 44 | 45 | # @return [Boolean] +true+ if the parameter is a splat argument 46 | def splat? 47 | self[:splat?] 48 | end 49 | 50 | # @return [Boolean] +true+ if the type of the parameter is defined 51 | def typed? 52 | self[:typed?] 53 | end 54 | 55 | # @return [Boolean] +true+ if the parameter is mentioned in the docs, 56 | # but not present in the method's signature 57 | def wrongly_mentioned? 58 | self[:wrongly_mentioned?] 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/code_object/member_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module CodeObject 5 | # Proxy class for types 6 | class MemberObject < Base 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/code_object/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module CodeObject 5 | # Proxy class for modules 6 | class ModuleObject < Base 7 | MANY_CHILDREN_THRESHOLD = 20 8 | def has_many_children? 9 | children.size > MANY_CHILDREN_THRESHOLD 10 | end 11 | 12 | def has_methods? 13 | children.any?(&:method?) 14 | end 15 | 16 | def pure_namespace? 17 | children.all?(&:namespace?) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/evaluation/base.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Evaluation 5 | # Base class for all JavaScript related evaluations 6 | # 7 | # @abstract 8 | class Base < Inch::Evaluation::Proxy 9 | protected 10 | 11 | def relevant_base_roles 12 | { 13 | Role::Object::InRoot => nil, 14 | Role::Object::Public => nil, 15 | Role::Object::Protected => nil, 16 | Role::Object::Private => nil, 17 | Role::Object::TaggedAsNodoc => nil, 18 | Role::Object::WithDoc => score_for(:docstring), 19 | Role::Object::WithoutDoc => score_for(:docstring), 20 | Role::Object::WithCodeExample => score_for(:code_example_single), 21 | Role::Object::WithMultipleCodeExamples => 22 | score_for(:code_example_multi), 23 | Role::Object::WithoutCodeExample => 24 | score_for(:code_example_single) 25 | } 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/evaluation/class_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Evaluation 5 | # Proxy class for classes 6 | class ClassObject < ModuleObject 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/evaluation/function_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Evaluation 5 | # Proxy class for functions 6 | class FunctionObject < Base 7 | def evaluate 8 | super 9 | evaluate_parameters 10 | end 11 | 12 | protected 13 | 14 | def relevant_roles 15 | relevant_base_roles.merge(relevant_function_roles) 16 | end 17 | 18 | private 19 | 20 | def evaluate_parameters 21 | params = object.parameters 22 | per_param = score_for_single_parameter 23 | params.each do |param| 24 | role_classes = relevant_parameter_roles(param, per_param) 25 | __evaluate(param, role_classes) 26 | end 27 | end 28 | 29 | def relevant_function_roles 30 | { 31 | Role::Function::Getter => nil, 32 | Role::Function::Setter => nil, 33 | Role::Function::Overridden => 34 | if object.overridden? 35 | object.overridden_method.score 36 | else 37 | nil 38 | end 39 | } 40 | end 41 | 42 | def relevant_parameter_roles(_param, per_param) 43 | { 44 | Role::FunctionParameter::WithWrongMention => 45 | -score_for(:parameters), 46 | Role::FunctionParameter::WithMention => per_param * 0.5, 47 | Role::FunctionParameter::WithoutMention => per_param * 0.5, 48 | Role::FunctionParameter::WithType => per_param * 0.5, 49 | Role::FunctionParameter::WithoutType => per_param * 0.5, 50 | Role::FunctionParameter::WithBadName => nil, 51 | } 52 | end 53 | 54 | def score_for_single_parameter 55 | @param_score ||= score_for(:parameters) / object.parameters.size 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/evaluation/member_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Evaluation 5 | # Proxy class for types 6 | class MemberObject < Base 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/evaluation/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Evaluation 5 | # Proxy class for modules 6 | class ModuleObject < Base 7 | protected 8 | 9 | def relevant_roles 10 | relevant_base_roles.merge(relevant_namespace_roles) 11 | end 12 | 13 | # @see Evaluation::Ruby::Base 14 | def relevant_namespace_roles 15 | { 16 | Role::Module::WithoutChildren => nil, 17 | Role::Module::WithChildren => nil, 18 | Role::Module::WithManyChildren => nil, 19 | Role::Module::WithoutMethods => nil, 20 | Role::Module::Pure => nil 21 | } 22 | end 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/import.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | end 5 | end 6 | end 7 | 8 | require 'inch/language/javascript/provider/jsdoc' 9 | 10 | require 'inch/language/javascript/code_object/base' 11 | require 'inch/language/javascript/code_object/module_object' 12 | require 'inch/language/javascript/code_object/class_object' 13 | require 'inch/language/javascript/code_object/function_object' 14 | require 'inch/language/javascript/code_object/member_object' 15 | 16 | require 'inch/language/javascript/evaluation/base' 17 | require 'inch/language/javascript/evaluation/module_object' 18 | require 'inch/language/javascript/evaluation/class_object' 19 | require 'inch/language/javascript/evaluation/function_object' 20 | require 'inch/language/javascript/evaluation/member_object' 21 | 22 | require 'inch/language/javascript/roles/base' 23 | require 'inch/language/javascript/roles/object' 24 | require 'inch/language/javascript/roles/module' 25 | require 'inch/language/javascript/roles/function' 26 | require 'inch/language/javascript/roles/function_parameter' 27 | require 'inch/language/javascript/roles/member' 28 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/provider/jsdoc.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module Inch 4 | module Language 5 | module JavaScript 6 | module Provider 7 | # Parses the source tree (using JSDoc) 8 | module JSDoc 9 | # @see Provider.parse 10 | def self.parse(dir, config) 11 | Parser.parse(dir, config) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | end 18 | 19 | require 'inch/language/javascript/provider/jsdoc/parser' 20 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/provider/jsdoc/object.rb: -------------------------------------------------------------------------------- 1 | require 'inch/language/javascript/provider/jsdoc/object/base' 2 | require 'inch/language/javascript/provider/jsdoc/object/function_object' 3 | require 'inch/language/javascript/provider/jsdoc/object/module_object' 4 | require 'inch/language/javascript/provider/jsdoc/object/class_object' 5 | require 'inch/language/javascript/provider/jsdoc/object/member_object' 6 | 7 | module Inch 8 | module Language 9 | module JavaScript 10 | module Provider 11 | module JSDoc 12 | # CodeObject::Provider::JSDoc::Object object represent code objects. 13 | # 14 | module Object 15 | class << self 16 | def clear_cache 17 | @cache = {} 18 | end 19 | 20 | # Returns a Proxy object for the given +jsdoc_object+ 21 | # 22 | # @param jsdoc_object [Hash] 23 | # @return [Provider::JSDoc::Object] 24 | def for(jsdoc_object) 25 | @cache ||= {} 26 | key = cache_key(jsdoc_object) 27 | if proxy_object = @cache[key] 28 | proxy_object 29 | else 30 | @cache[key] = class_for(jsdoc_object).new(jsdoc_object) 31 | end 32 | end 33 | 34 | private 35 | 36 | # Returns a Proxy class for the given +json_object+ 37 | # 38 | # @param json_object [Hash] 39 | # @return [Class] 40 | def class_for(json_object) 41 | class_name = json_object['kind'].capitalize + 'Object' 42 | JSDoc::Object.const_get(class_name) 43 | rescue NameError 44 | JSDoc::Object::Base 45 | end 46 | 47 | # Returns a cache key for the given +jsdoc_object+ 48 | # 49 | # @param jsdoc_object [Hash] 50 | # @return [String] 51 | def cache_key(jsdoc_object) 52 | return if jsdoc_object['meta'].nil? 53 | "#{jsdoc_object['longname']}/" \ 54 | "#{jsdoc_object['meta']['path']}/" \ 55 | "#{jsdoc_object['meta']['filename']}:" \ 56 | "#{jsdoc_object['meta']['lineno']}" 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/provider/jsdoc/object/class_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Provider 5 | module JSDoc 6 | module Object 7 | # Proxy class for classes 8 | class ClassObject < Base 9 | def namespace? 10 | true 11 | end 12 | end 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/provider/jsdoc/object/function_object.rb: -------------------------------------------------------------------------------- 1 | require 'inch/language/javascript/provider/jsdoc/object/function_parameter_object' 2 | 3 | module Inch 4 | module Language 5 | module JavaScript 6 | module Provider 7 | module JSDoc 8 | module Object 9 | # Proxy class for functions 10 | class FunctionObject < Base 11 | def method? 12 | true 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/provider/jsdoc/object/function_parameter_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Provider 5 | module JSDoc 6 | module Object 7 | # Proxy class for function parameters 8 | class FunctionParameterObject 9 | attr_reader :name # @return [String] 10 | 11 | # @param method [YARD::Object::MethodObject] the method the 12 | # parameter belongs to 13 | # @param name [String] the name of the parameter 14 | def initialize(method, name) 15 | @method = method 16 | @name = name 17 | end 18 | 19 | # @return [Boolean] +true+ if the parameter is a block 20 | def block? 21 | false 22 | end 23 | 24 | # @return [Boolean] +true+ if an additional description is given? 25 | def described? 26 | described_by_docstring? 27 | end 28 | 29 | # @return [Boolean] +true+ if the parameter is mentioned in the 30 | # docs 31 | def mentioned? 32 | mentioned_by_docstring? 33 | end 34 | 35 | # @return [Boolean] +true+ if the parameter is a splat argument 36 | def splat? 37 | false 38 | end 39 | 40 | # @return [Boolean] +true+ if the type of the parameter is defined 41 | def typed? 42 | described_by_docstring? 43 | end 44 | 45 | # @return [Boolean] +true+ if the parameter is mentioned in the 46 | # docs, but not present in the method's signature 47 | def wrongly_mentioned? 48 | false 49 | end 50 | 51 | private 52 | 53 | def described_by_docstring? 54 | @method.docstring.describes_parameter?(name) 55 | end 56 | 57 | def mentioned_by_docstring? 58 | @method.docstring.mentions_parameter?(name) 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/provider/jsdoc/object/member_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Provider 5 | module JSDoc 6 | module Object 7 | # Proxy class for types 8 | class MemberObject < Base 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/provider/jsdoc/object/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Provider 5 | module JSDoc 6 | module Object 7 | # Proxy class for modules 8 | class ModuleObject < Base 9 | def namespace? 10 | true 11 | end 12 | end 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/provider/jsdoc/parser.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'inch/language/javascript/provider/jsdoc/object' 3 | 4 | module Inch 5 | module Language 6 | module JavaScript 7 | module Provider 8 | module JSDoc 9 | # Parses the source tree (using JSDoc) 10 | class Parser 11 | # TODO: should we remove constant and namespace from this list? 12 | IGNORE_TYPES = %w(event external file interface member mixin package param constant namespace) 13 | 14 | attr_reader :parsed_objects 15 | 16 | # Helper method to parse an instance with the given +args+ 17 | # 18 | # @see #parse 19 | # @return [CodeObject::Provider::JSDoc::Parser] the instance that 20 | # ran 21 | def self.parse(*args) 22 | parser = new 23 | parser.parse(*args) 24 | parser 25 | end 26 | 27 | # @param dir [String] directory 28 | # @param config [Inch::Config::Codebase] configuration for codebase 29 | # @return [void] 30 | def parse(dir, config) 31 | raise "Directory does not exist: #{dir}" if !File.exist?(dir) 32 | Dir.chdir(dir) do 33 | parse_objects(config.included_files, config.excluded_files, 34 | config.read_dump_file) 35 | end 36 | end 37 | 38 | # @return [Array] 39 | def objects 40 | @objects ||= parsed_objects.map do |o| 41 | JSDoc::Object.for(o) unless IGNORE_TYPES.include?(o['kind']) 42 | end.compact.uniq 43 | end 44 | 45 | private 46 | 47 | def parse_objects(_paths, _excluded, read_dump_file = nil) 48 | if read_dump_file.nil? 49 | fail 'JavaScript analysis only works with --read-from-dump.' 50 | else 51 | output = File.read(read_dump_file, :encoding => 'utf-8') 52 | end 53 | @parsed_objects = JSON[output]['objects'] 54 | end 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/roles/base.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Evaluation 5 | module Role 6 | # @abstract 7 | class Base < Inch::Evaluation::Role 8 | # Returns the type of the +object+ that is being evaluated. 9 | def object_type 10 | object.class.to_s.split('::').last.gsub(/Object$/, '').downcase 11 | end 12 | end 13 | 14 | # Missing is the base class for things that can be improved in the doc 15 | # 16 | class Missing < Base 17 | def score 18 | nil 19 | end 20 | 21 | # @return [Fixnum] 22 | # a score that can be achieved by adding the missing thing 23 | # mentioned by the role 24 | def potential_score 25 | @value.to_i 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/roles/member.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Evaluation 5 | module Role 6 | # Roles assigned to members 7 | module Member 8 | end 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/inch/language/javascript/roles/module.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module JavaScript 4 | module Evaluation 5 | module Role 6 | # Roles assigned to modules 7 | module Module 8 | # Role assigned to modules with children 9 | # 10 | # @see CodeObject::Ruby::NamespaceObject#has_children? 11 | class WithChildren < Base 12 | applicable_if :has_children? 13 | 14 | # This role doesnot assign a score. 15 | def score 16 | 0 17 | end 18 | 19 | # This role sets a max_score. 20 | def max_score 21 | # @value.to_f 22 | end 23 | end 24 | 25 | # Role assigned to modules with many children 26 | # 27 | # @see CodeObject::Ruby::NamespaceObject#has_many_children? 28 | class WithManyChildren < Base 29 | applicable_if :has_many_children? 30 | 31 | # +priority 32 | def priority 33 | +1 34 | end 35 | end 36 | 37 | # Role assigned to modules without any children 38 | class WithoutChildren < Base 39 | applicable_unless :has_children? 40 | end 41 | 42 | # Role assigned to modules without any methods 43 | class WithoutMethods < Base 44 | applicable_unless :has_methods? 45 | 46 | def priority 47 | -2 48 | end 49 | end 50 | 51 | # A 'pure' namespace has only modules as children 52 | class Pure < Base 53 | applicable_if :pure_namespace? 54 | 55 | def priority 56 | -2 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/code_object/class_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module CodeObject 5 | class ClassObject < NamespaceObject 6 | end 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/code_object/class_variable_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module CodeObject 5 | class ClassVariableObject < Base 6 | end 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/code_object/constant_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module CodeObject 5 | class ConstantObject < Base 6 | end 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/code_object/method_parameter_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module CodeObject 5 | # Proxy class for method parameters 6 | class MethodParameterObject 7 | def initialize(attributes) 8 | @attributes = attributes 9 | end 10 | 11 | def [](key) 12 | @attributes[key] 13 | end 14 | 15 | BAD_NAME_EXCEPTIONS = %w(id) 16 | BAD_NAME_THRESHOLD = 3 17 | 18 | # @return [Boolean] +true+ if the name of the parameter is 19 | # uncommunicative 20 | def bad_name? 21 | return false if BAD_NAME_EXCEPTIONS.include?(name) 22 | name.size < BAD_NAME_THRESHOLD || name =~ /[0-9]$/ 23 | end 24 | 25 | # @return [Boolean] +true+ if the parameter is a block 26 | def block? 27 | self[:block?] 28 | end 29 | 30 | # @return [Boolean] +true+ if an additional description given? 31 | def described? 32 | self[:described?] 33 | end 34 | 35 | # @return [Boolean] +true+ if the parameter is mentioned in the docs 36 | def mentioned? 37 | self[:mentioned?] 38 | end 39 | 40 | def name 41 | self[:name] 42 | end 43 | alias_method :fullname, :name 44 | 45 | # @return [Boolean] +true+ if the parameter is a splat argument 46 | def splat? 47 | self[:splat?] 48 | end 49 | 50 | # @return [Boolean] +true+ if the type of the parameter is defined 51 | def typed? 52 | self[:typed?] 53 | end 54 | 55 | # @return [Boolean] +true+ if the parameter is mentioned in the docs, 56 | # but not present in the method's signature 57 | def wrongly_mentioned? 58 | self[:wrongly_mentioned?] 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/code_object/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module CodeObject 5 | class ModuleObject < NamespaceObject 6 | end 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/code_object/namespace_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module CodeObject 5 | # a namespace object can have methods and other namespace objects 6 | # inside itself (e.g. classes and modules) 7 | class NamespaceObject < Base 8 | # The wording is a bit redundant, but this means the class and 9 | # instance attributes of the namespace 10 | def attributes 11 | self[:attributes] 12 | end 13 | 14 | MANY_ATTRIBUTES_THRESHOLD = 5 15 | def has_many_attributes? 16 | attributes.size > MANY_ATTRIBUTES_THRESHOLD 17 | end 18 | 19 | MANY_CHILDREN_THRESHOLD = 20 20 | def has_many_children? 21 | children.size > MANY_CHILDREN_THRESHOLD 22 | end 23 | 24 | def has_methods? 25 | children.any?(&:method?) 26 | end 27 | 28 | def pure_namespace? 29 | children.all?(&:namespace?) 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/evaluation/base.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | # Base class for all Ruby related evaluations 6 | # 7 | # @abstract 8 | class Base < Inch::Evaluation::Proxy 9 | protected 10 | 11 | def relevant_base_roles 12 | { 13 | Role::Object::InRoot => nil, 14 | Role::Object::Public => nil, 15 | Role::Object::Protected => nil, 16 | Role::Object::Private => nil, 17 | Role::Object::TaggedAsNodoc => nil, 18 | Role::Object::WithDoc => score_for(:docstring), 19 | Role::Object::WithoutDoc => score_for(:docstring), 20 | Role::Object::WithCodeExample => score_for(:code_example_single), 21 | Role::Object::WithMultipleCodeExamples => 22 | score_for(:code_example_multi), 23 | Role::Object::WithoutCodeExample => 24 | score_for(:code_example_single), 25 | Role::Object::Tagged => score_for_unconsidered_tags, 26 | Role::Object::TaggedAsAPI => nil, 27 | Role::Object::TaggedAsInternalAPI => nil, 28 | Role::Object::TaggedAsPrivate => nil, 29 | Role::Object::Alias => 30 | if object.alias? 31 | aliased_object = object.aliased_object 32 | if aliased_object.alias? && aliased_object.aliased_object.alias? 33 | # warn "Possible alias cycle: #{object.fullname} -> #{aliased_object.fullname}" 34 | nil 35 | else 36 | aliased_object.score 37 | end 38 | else 39 | nil 40 | end 41 | } 42 | end 43 | 44 | def score_for_unconsidered_tags 45 | count = object.unconsidered_tag_count 46 | score_for(:unconsidered_tag) * count 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/evaluation/class_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | class ClassObject < NamespaceObject 6 | end 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/evaluation/class_variable_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | class ClassVariableObject < Base 6 | protected 7 | 8 | def relevant_roles 9 | { 10 | Role::ClassVariable::WithDoc => score_for(:docstring), 11 | Role::ClassVariable::WithoutDoc => score_for(:docstring), 12 | Role::ClassVariable::TaggedAsNodoc => nil, 13 | Role::ClassVariable::Public => nil, 14 | Role::ClassVariable::Private => nil 15 | } 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/evaluation/constant_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | class ConstantObject < Base 6 | protected 7 | 8 | def relevant_roles 9 | { 10 | Role::Constant::WithDoc => score_for(:docstring), 11 | Role::Constant::WithoutDoc => score_for(:docstring), 12 | Role::Constant::TaggedAsNodoc => nil, 13 | Role::Constant::Public => nil, 14 | Role::Constant::Private => nil 15 | } 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/evaluation/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | class ModuleObject < NamespaceObject 6 | end 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/evaluation/namespace_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | # a namespace object can have methods and other namespace objects 6 | # inside itself (e.g. classes and modules) 7 | class NamespaceObject < Base 8 | protected 9 | 10 | def relevant_roles 11 | relevant_base_roles.merge(relevant_namespace_roles) 12 | end 13 | 14 | # @see Evaluation::Ruby::Base 15 | def relevant_namespace_roles 16 | { 17 | Role::Namespace::Core => nil, 18 | Role::Namespace::WithManyAttributes => nil, 19 | Role::Namespace::WithoutChildren => nil, 20 | Role::Namespace::WithChildren => nil, 21 | Role::Namespace::WithManyChildren => nil, 22 | Role::Namespace::WithoutMethods => nil, 23 | Role::Namespace::Pure => nil 24 | } 25 | end 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/import.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | end 5 | end 6 | end 7 | 8 | require 'inch/language/ruby/provider/yard' 9 | 10 | require 'inch/language/ruby/code_object/base' 11 | require 'inch/language/ruby/code_object/namespace_object' 12 | require 'inch/language/ruby/code_object/class_object' 13 | require 'inch/language/ruby/code_object/class_variable_object' 14 | require 'inch/language/ruby/code_object/constant_object' 15 | require 'inch/language/ruby/code_object/method_object' 16 | require 'inch/language/ruby/code_object/method_parameter_object' 17 | require 'inch/language/ruby/code_object/module_object' 18 | 19 | require 'inch/language/ruby/evaluation/base' 20 | require 'inch/language/ruby/evaluation/namespace_object' 21 | require 'inch/language/ruby/evaluation/class_object' 22 | require 'inch/language/ruby/evaluation/class_variable_object' 23 | require 'inch/language/ruby/evaluation/constant_object' 24 | require 'inch/language/ruby/evaluation/method_object' 25 | require 'inch/language/ruby/evaluation/module_object' 26 | 27 | require 'inch/language/ruby/roles/base' 28 | require 'inch/language/ruby/roles/missing' 29 | require 'inch/language/ruby/roles/object' 30 | require 'inch/language/ruby/roles/method' 31 | require 'inch/language/ruby/roles/method_parameter' 32 | require 'inch/language/ruby/roles/namespace' 33 | require 'inch/language/ruby/roles/constant' 34 | require 'inch/language/ruby/roles/class_variable' 35 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/provider/yard.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Provider 5 | # Parses the source tree (using YARD) 6 | module YARD 7 | # Returns +true+ if the docstring was generated by YARD 8 | # 9 | # @param docstring [Docstring,String] 10 | # @param method [MethodObject] 11 | def self.implicit_docstring?(docstring, method) 12 | name = method.name 13 | if method.getter? 14 | docstring.to_s == "Returns the value of attribute #{name}" 15 | elsif method.setter? 16 | basename = name.to_s.gsub(/(\=)$/, '') 17 | docstring.to_s == "Sets the attribute #{basename}" 18 | else 19 | false 20 | end 21 | end 22 | 23 | # Returns +true+ if the tag was generated by YARD 24 | # 25 | # @param tag [::YARD::Tag] 26 | # @param method [MethodObject] 27 | def self.implicit_tag?(tag, method) 28 | name = method.name 29 | if method.getter? 30 | tag.tag_name == 'return' && 31 | tag.text == "the current value of #{name}" 32 | elsif method.setter? 33 | tag.tag_name == 'return' && 34 | tag.text == 'the newly set value' 35 | else 36 | false 37 | end 38 | end 39 | 40 | # @see Provider.parse 41 | def self.parse(dir, config) 42 | Parser.parse(dir, config) 43 | end 44 | end 45 | end 46 | end 47 | end 48 | end 49 | 50 | require 'logger' 51 | require 'yard' 52 | 53 | log.level = ::Logger::UNKNOWN # basically disable YARD's logging 54 | 55 | require 'inch/language/ruby/provider/yard/parser' 56 | require 'inch/language/ruby/provider/yard/docstring' 57 | require 'inch/language/ruby/provider/yard/nodoc_helper' 58 | require 'inch/language/ruby/provider/yard/object' 59 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/provider/yard/object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Provider 5 | module YARD 6 | # CodeObject::Provider::YARD::Object object represent code objects. 7 | # 8 | module Object 9 | class << self 10 | def clear_cache 11 | @cache = {} 12 | end 13 | 14 | # Returns a Proxy object for the given +yard_object+ 15 | # 16 | # @param yard_object [YARD::CodeObject] 17 | # @return [Provider::YARD::Object] 18 | def for(yard_object) 19 | @cache ||= {} 20 | if (proxy_object = @cache[cache_key(yard_object)]) 21 | proxy_object 22 | else 23 | @cache[cache_key(yard_object)] = 24 | class_for(yard_object).new(yard_object) 25 | end 26 | end 27 | 28 | private 29 | 30 | # Returns a Proxy class for the given +yard_object+ 31 | # 32 | # @param yard_object [YARD::CodeObject] 33 | # @return [Class] 34 | def class_for(yard_object) 35 | class_name = yard_object.class.to_s.split('::').last 36 | const_get(class_name) 37 | rescue NameError 38 | Base 39 | end 40 | 41 | # Returns a cache key for the given +yard_object+ 42 | # 43 | # @param yard_object [YARD::CodeObject] 44 | # @return [String] 45 | def cache_key(yard_object) 46 | yard_object.path 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | end 55 | 56 | require 'inch/language/ruby/provider/yard/object/base' 57 | require 'inch/language/ruby/provider/yard/object/namespace_object' 58 | require 'inch/language/ruby/provider/yard/object/class_object' 59 | require 'inch/language/ruby/provider/yard/object/class_variable_object' 60 | require 'inch/language/ruby/provider/yard/object/constant_object' 61 | require 'inch/language/ruby/provider/yard/object/method_object' 62 | require 'inch/language/ruby/provider/yard/object/method_parameter_object' 63 | require 'inch/language/ruby/provider/yard/object/module_object' 64 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/provider/yard/object/class_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Provider 5 | module YARD 6 | module Object 7 | class ClassObject < NamespaceObject 8 | end 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/provider/yard/object/class_variable_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Provider 5 | module YARD 6 | module Object 7 | class ClassVariableObject < Base 8 | end 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/provider/yard/object/constant_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Provider 5 | module YARD 6 | module Object 7 | class ConstantObject < Base 8 | end 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/provider/yard/object/module_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Provider 5 | module YARD 6 | module Object 7 | class ModuleObject < NamespaceObject 8 | end 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/provider/yard/object/namespace_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Provider 5 | module YARD 6 | module Object 7 | # a namespace object can have methods and other namespace objects 8 | # inside itself (e.g. classes and modules) 9 | class NamespaceObject < Base 10 | def attributes 11 | object.class_attributes.values + 12 | object.instance_attributes.values 13 | end 14 | 15 | def children_fullnames 16 | children.map(&:fullname) 17 | end 18 | 19 | def namespace? 20 | true 21 | end 22 | 23 | def has_methods? 24 | children.any?(&:method?) 25 | end 26 | 27 | def pure_namespace? 28 | children.all?(&:namespace?) 29 | end 30 | 31 | # called by MethodObject#getter? 32 | def child(name) 33 | children.find { |child| child.name == name } if children 34 | end 35 | 36 | def children 37 | object.children.map do |o| 38 | YARD::Object.for(o) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/provider/yard/object/root_object.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Provider 5 | module YARD 6 | module Object 7 | class RootObject < NamespaceObject 8 | end 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/roles/base.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | module Role 6 | # @abstract 7 | class Base < Inch::Evaluation::Role 8 | # Returns the type of the +object+ that is being evaluated. 9 | def object_type 10 | object.class.to_s.split('::').last.gsub(/Object$/, '').downcase 11 | end 12 | end 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/roles/class_variable.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | module Role 6 | # Roles assigned to class variables 7 | module ClassVariable 8 | class WithDoc < Object::WithDoc 9 | applicable_if :has_doc? 10 | end 11 | class WithoutDoc < Object::WithoutDoc 12 | applicable_unless :has_doc? 13 | end 14 | 15 | class TaggedAsNodoc < Object::TaggedAsNodoc 16 | applicable_if :nodoc? 17 | end 18 | class InRoot < Object::InRoot 19 | applicable_if :in_root? 20 | end 21 | 22 | class Public < Object::Public 23 | applicable_if :public? 24 | priority(-1) 25 | end 26 | class Private < Object::Private 27 | applicable_if :private? 28 | priority(-3) 29 | end 30 | 31 | class WithCodeExample < Object::WithCodeExample 32 | applicable_if do |o| 33 | o.has_code_example? && !o.has_multiple_code_examples? 34 | end 35 | end 36 | 37 | class WithMultipleCodeExamples < Object::WithMultipleCodeExamples 38 | applicable_if :has_multiple_code_examples? 39 | end 40 | 41 | class WithoutCodeExample < Object::WithoutCodeExample 42 | applicable_unless :has_code_example? 43 | 44 | def suggestion 45 | nil 46 | end 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/roles/constant.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | module Role 6 | # Roles assigned to constants 7 | module Constant 8 | class WithDoc < Object::WithDoc 9 | applicable_if :has_doc? 10 | end 11 | class WithoutDoc < Object::WithoutDoc 12 | applicable_unless :has_doc? 13 | end 14 | 15 | class TaggedAsNodoc < Object::TaggedAsNodoc 16 | applicable_if :nodoc? 17 | end 18 | class InRoot < Object::InRoot 19 | applicable_if :in_root? 20 | end 21 | 22 | class Public < Object::Public 23 | applicable_if :public? 24 | priority(-1) 25 | end 26 | class Private < Object::Private 27 | applicable_if :private? 28 | priority(-3) 29 | end 30 | 31 | class WithCodeExample < Object::WithCodeExample 32 | applicable_if do |o| 33 | o.has_code_example? && !o.has_multiple_code_examples? 34 | end 35 | end 36 | 37 | class WithMultipleCodeExamples < Object::WithMultipleCodeExamples 38 | applicable_if :has_multiple_code_examples? 39 | end 40 | 41 | class WithoutCodeExample < Object::WithoutCodeExample 42 | applicable_unless :has_code_example? 43 | 44 | def suggestion 45 | nil 46 | end 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/inch/language/ruby/roles/missing.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Language 3 | module Ruby 4 | module Evaluation 5 | module Role 6 | # Missing is the base class for things that can be improved in the doc 7 | # 8 | class Missing < Base 9 | def score 10 | nil 11 | end 12 | 13 | # @return [Fixnum] 14 | # a score that can be achieved by adding the missing thing 15 | # mentioned by the role 16 | def potential_score 17 | @value.to_i 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/inch/rake.rb: -------------------------------------------------------------------------------- 1 | require 'inch' 2 | require 'inch/rake/suggest' 3 | -------------------------------------------------------------------------------- /lib/inch/rake/suggest.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rake' 4 | require 'rake/tasklib' 5 | require 'inch/cli' 6 | 7 | module Inch 8 | # Holds all Rake tasks 9 | module Rake 10 | # Provides Rake task integration 11 | class Suggest < ::Rake::TaskLib 12 | # @return [String] name of the Rake task 13 | attr_accessor :name 14 | # @return [Array] arguments to be passed to Suggest.run 15 | attr_accessor :args 16 | 17 | # @param name [String] name of the Rake task 18 | # @param *args [Array] arguments to be passed to Suggest.run 19 | # @param &block [Proc] optional, evaluated inside the task definition 20 | def initialize(name = 'inch', *args, &block) 21 | @name = name 22 | @args = args 23 | block.call(self) if block 24 | 25 | desc 'Suggest objects to add documention to' 26 | task(@name) { suggest } 27 | end 28 | 29 | # @return [void] 30 | def suggest 31 | ::Inch::CLI::Command::Suggest.run(*@args) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/inch/utils/buffered_ui.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Utils 3 | class BufferedUI < UI 4 | attr_reader :out, :err 5 | 6 | def initialize(_stdout = $stdout, _stderr = $stderr) 7 | @io = StringIO.new 8 | super(@io, @io) 9 | end 10 | 11 | def buffer 12 | @io.string 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/inch/utils/code_location.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Utils 3 | # CodeLocation is a utility class to find declarations of objects 4 | # in files 5 | class CodeLocation < Struct.new(:base_dir, :relative_path, 6 | :line_no) 7 | def filename 8 | File.join(base_dir, relative_path) 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/inch/utils/read_write_methods.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Utils 3 | # Extend a class with ReadWriteMethods and you will gain the +rw_methods+ 4 | # macro. 5 | # 6 | # class User 7 | # extend ReadWriteMethods 8 | # rw_methods :name, :admin 9 | # end 10 | # 11 | # This implements read methods that act as writer methods when called 12 | # with a value parameter: 13 | # 14 | # user = User.new 15 | # user.name # => nil 16 | # user.name "Adam" 17 | # user.name # => "Adam" 18 | # 19 | module ReadWriteMethods 20 | # Implements a read method that acts as writer if called with a value 21 | # 22 | # @return [void] 23 | def rw_method(name) 24 | class_eval ''" 25 | def #{name}(value = nil) 26 | if value.nil? 27 | @#{name} 28 | else 29 | @#{name} = value 30 | end 31 | end 32 | "'' 33 | end 34 | 35 | # Implements multiple read(write) methods with the given +names+ 36 | # 37 | # @param names [Array] 38 | # @return [void] 39 | def rw_methods(*names) 40 | [names].flatten.each { |name| rw_method(name) } 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/inch/utils/shell_helper.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | module Utils 3 | module ShellHelper 4 | def git(dir, command) 5 | Dir.chdir(dir) do 6 | sh("git #{command}") 7 | end 8 | end 9 | 10 | def sh(command) 11 | `#{command}` 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/inch/utils/ui.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Inch 4 | module Utils 5 | class UI 6 | attr_reader :out, :err 7 | 8 | def initialize(stdout = $stdout, stderr = $stderr) 9 | @out, @err = stdout, stderr 10 | end 11 | 12 | def debug(msg) 13 | return unless ENV['DEBUG'] 14 | msg.to_s.lines.each do |line| 15 | trace edged :dark, line.gsub(/\n$/, '').color(:dark) 16 | end 17 | end 18 | 19 | def sub(msg = '') 20 | color = @current_header_color || :white 21 | trace __edged(color, msg) 22 | end 23 | 24 | def edged(color, msg, edge = '┃ ') 25 | trace __edged(color, msg, edge) 26 | end 27 | 28 | # Writes the given +text+ to out 29 | # 30 | # @param text [String] 31 | # @return [void] 32 | def trace(text = '') 33 | @current_header_color = nil if text.to_s.empty? 34 | out.puts text 35 | end 36 | 37 | # Writes the given +text+ to err 38 | # 39 | # @param text [String] 40 | # @return [void] 41 | def warn(text = '') 42 | err.puts text 43 | end 44 | 45 | def header(text, color, bg_color = nil) 46 | @current_header_color = color 47 | trace __header(text, color, bg_color) 48 | trace unless use_color? 49 | end 50 | 51 | # @return [Boolean] true if the UI uses coloring 52 | def use_color? 53 | Term::ANSIColor.coloring? 54 | end 55 | 56 | private 57 | 58 | def __edged(color, msg, edge = '┃ ') 59 | edge.color(color) + msg 60 | end 61 | 62 | def __header(text, color, bg_color = nil) 63 | bg_color ||= "intense_#{color}" 64 | bar = " #{text}".ljust(CLI::COLUMNS - 1) 65 | .on_color(bg_color).color(:color16) 66 | '#'.color(color).on_color(color) + bar 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/inch/version.rb: -------------------------------------------------------------------------------- 1 | module Inch 2 | VERSION = '0.9.0.rc1' 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/ruby/alias_cycle/lib/alias.rb: -------------------------------------------------------------------------------- 1 | class ViciousAliasCycle < Hash #:nodoc: 2 | # horrible hack to restore Hash#delete 3 | alias :__delete__ :delete 4 | include BaseApi 5 | alias :api_delete :delete 6 | alias :delete :__delete__ 7 | remove_method :__delete__ 8 | end 9 | -------------------------------------------------------------------------------- /test/fixtures/ruby/diff1/lib/diff1.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | # A complicated method 3 | def b(o, i, *args, &block) 4 | # ... snip ... 5 | end 6 | 7 | # An example of a method that takes a parameter (+param1+) 8 | # and does nothing. 9 | # 10 | # Returns nil 11 | def c(param1) 12 | end 13 | 14 | def d 15 | "#{self.class}_#{id}.foo" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/fixtures/ruby/diff2/lib/diff2.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | # New method 3 | # 4 | # Returns a string. 5 | def a 6 | end 7 | 8 | # A complicated method 9 | # 10 | # @param o [String] 11 | # @param i [String] 12 | # @param args [Array] 13 | # @param block [Proc] 14 | # @return [void] 15 | def b(o, i, *args, &block) 16 | # ... snip ... 17 | end 18 | 19 | # An example of a method that takes a parameter (+param1+) 20 | # and does nothing. 21 | # 22 | # Returns nil 23 | def c(param1) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/fixtures/ruby/inch-yml/.inch.yml: -------------------------------------------------------------------------------- 1 | files: 2 | included: 3 | - foo/**/*.rb 4 | excluded: 5 | - foo/vendor/**/*.rb 6 | # or 7 | - !ruby/regexp /vendor/ 8 | -------------------------------------------------------------------------------- /test/fixtures/ruby/inch-yml/foo/bar.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | class Bar 3 | def initialize 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/fixtures/ruby/inch-yml/foo/vendor/base.rb: -------------------------------------------------------------------------------- 1 | module Vendor 2 | class Base 3 | def initialize 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/fixtures/ruby/parameters/lib/foo.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | # A complicated method 3 | def complicated(o, i, *args, &block) 4 | # ... snip ... 5 | end 6 | 7 | # The problem here is that ... 8 | # 9 | # @param *names [Array] 10 | # @return [String] 11 | def method_with_splat_parameter(*names) 12 | end 13 | 14 | # The problem here is that ... 15 | # 16 | # @param names [Array] 17 | # @return [String] 18 | def method_with_splat_parameter2(*names) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/fixtures/ruby/readme/lib/foo.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | # A complicated method 3 | def complicated(o, i, *args, &block) 4 | # ... snip ... 5 | end 6 | 7 | # An example of a method that takes a parameter (+param1+) 8 | # and does nothing. 9 | # 10 | # Returns nil 11 | def nothing(param1) 12 | end 13 | 14 | def filename 15 | "#{self.class}_#{id}.foo" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/fixtures/ruby/really_good/lib/foo.rb: -------------------------------------------------------------------------------- 1 | # Foo is the classical name for a module 2 | class Foo 3 | # A complicated method 4 | def complicated 5 | # ... snip ... 6 | end 7 | 8 | # An example of a method that takes a parameter (+param1+) 9 | # and does nothing. 10 | # 11 | # param1 - The first param 12 | # 13 | # Returns nil 14 | def nothing(param1) 15 | end 16 | 17 | private 18 | 19 | def filename 20 | "#{self.class}_#{id}.foo" 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/fixtures/ruby/really_good_pedantic/lib/foo.rb: -------------------------------------------------------------------------------- 1 | # Foo is the classical name for a module 2 | class Foo 3 | # A complicated method 4 | # @return [void] 5 | def complicated 6 | # ... snip ... 7 | end 8 | 9 | # An example of a method that takes a parameter (+param1+) 10 | # and does nothing. 11 | # 12 | # param1 - The first param 13 | # 14 | # Returns a String or nil 15 | def nothing(param1) 16 | end 17 | 18 | private 19 | 20 | def filename 21 | "#{self.class}_#{id}.foo" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/fixtures/ruby/simple/README: -------------------------------------------------------------------------------- 1 | # Inch 'simple' fixture 2 | 3 | Here goes the fixture description 4 | 5 | ## Installation 6 | 7 | Or install it yourself as: 8 | 9 | $ gem install inch 10 | 11 | ## Usage 12 | 13 | $ cd test/fixtures/simple 14 | 15 | $ be inch 16 | 17 | TODO: Write better usage instructions here 18 | 19 | ## Contributing 20 | 21 | 1. Fork it ( http://github.com/rrrene/inch/fork ) 22 | 2. Create your feature branch (`git checkout -b my-new-feature`) 23 | 3. Commit your changes (`git commit -am 'Add some feature'`) 24 | 4. Push to the branch (`git push origin my-new-feature`) 25 | 5. Create new Pull Request 26 | -------------------------------------------------------------------------------- /test/fixtures/ruby/simple/lib/broken_ruby_2_0_features.rb: -------------------------------------------------------------------------------- 1 | module Foo 2 | # Redirect to the given URL 3 | # @param url [String] the destination URL 4 | # @param status [Fixnum] the http code 5 | def method_with_named_parameter(url, status: 302) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/ruby/simple/lib/directives.rb: -------------------------------------------------------------------------------- 1 | # Representation of attributes of a user in the database 2 | # 3 | # @!attribute email 4 | # @return [String] E-mail address (from Devise) 5 | class Attributes 6 | # @return [String] Username (from Devise) 7 | attr_accessor :username 8 | end 9 | 10 | 11 | # Representation of attributes of a user in the database 12 | # 13 | class AttributesAccessor 14 | attr_accessor :username 15 | # @!attribute email 16 | # @return [String] E-mail address (from Devise) 17 | attr_accessor :email 18 | end 19 | 20 | 21 | # Representation of attributes of a user in the database 22 | # 23 | class AttributesStruct < Struct.new(:email, :username) 24 | # @!attribute email 25 | # @return [String] E-mail address (from Devise) 26 | # @!attribute username 27 | # @return [String] Username (from Devise) 28 | end 29 | 30 | -------------------------------------------------------------------------------- /test/fixtures/ruby/simple/lib/nodoc.rb: -------------------------------------------------------------------------------- 1 | module Foo 2 | class Qux # :nodoc: 3 | def method_with_implicit_nodoc 4 | end 5 | 6 | DOCCED_VALUE = 42 # :doc: 7 | 8 | class Quux 9 | def method_without_nodoc 10 | end 11 | 12 | PUBLIC_VALUE = :foo 13 | PRIVATE_VALUE = :bar # :nodoc: 14 | 15 | # @private 16 | def method_with_private_tag 17 | end 18 | 19 | def method_with_explicit_nodoc # :nodoc: 20 | end 21 | end 22 | end 23 | 24 | class HiddenClass #:nodoc: all 25 | def some_value 26 | end 27 | 28 | class EvenMoreHiddenClass 29 | def method_with_implicit_nodoc 30 | end 31 | 32 | class SuddenlyVisibleClass # :doc: 33 | def method_with_implicit_doc 34 | end 35 | end 36 | end 37 | end 38 | 39 | # @private 40 | class HiddenClassViaTag 41 | # @api private 42 | def some_value 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/fixtures/ruby/simple/lib/role_namespaces.rb: -------------------------------------------------------------------------------- 1 | 2 | # @deprecated 3 | # @see PureNamespace 4 | PUBLIC_ROOT_CONSTANT = :foo 5 | 6 | PRIVATE_ROOT_CONSTANT = :foo 7 | private_constant :PRIVATE_ROOT_CONSTANT 8 | 9 | class String 10 | def foobar 11 | self + 'foobar!' 12 | end 13 | end 14 | 15 | module InchTest 16 | # You would want to use it like this: 17 | # 18 | # CodeExample.new 19 | # 20 | class CodeExample 21 | end 22 | 23 | # You would want to use it like this: 24 | # 25 | # MultipleCodeExamples.new 26 | # 27 | # MultipleCodeExamples.new # => something 28 | # 29 | class MultipleCodeExamples 30 | end 31 | 32 | # You would want to use it like this: 33 | # 34 | # @example 35 | # MultipleCodeExamples2.new 36 | # MultipleCodeExamples2.new # => something 37 | # 38 | class MultipleCodeExamples2 39 | end 40 | 41 | module PureNamespace 42 | end 43 | 44 | # Some deprecated stuff 45 | # @since 0.1.0 46 | # @deprecated 47 | module Deprecated 48 | module ClassMethods 49 | end 50 | end 51 | 52 | # @deprecated 53 | # @see PureNamespace 54 | module ManyChildren 55 | class Base0; end 56 | class Base1; end 57 | class Base2; end 58 | class Base3; end 59 | class Base4; end 60 | class Base5; end 61 | class Base6; end 62 | class Base7; end 63 | class Base8; end 64 | class Base9; end 65 | class Base10; end 66 | class Base11; end 67 | class Base12; end 68 | class Base13; end 69 | class Base14; end 70 | class Base15; end 71 | class Base16; end 72 | class Base17; end 73 | class Base18; end 74 | class Base19; end 75 | class Base20; end 76 | class Base21; end 77 | end 78 | 79 | module ManyAttributes 80 | attr_accessor :base0 81 | attr_accessor :base1 82 | attr_accessor :base2 83 | attr_accessor :base3 84 | attr_accessor :base4 85 | attr_accessor :base5 86 | attr_accessor :base6 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /test/fixtures/ruby/visibility/lib/foo.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | def public_method 3 | end 4 | public :public_method 5 | 6 | 7 | def protected_method 8 | end 9 | protected :protected_method 10 | 11 | def private_method 12 | end 13 | private :private_method 14 | 15 | # @private 16 | def method_with_private_tag 17 | end 18 | end -------------------------------------------------------------------------------- /test/fixtures/ruby/yardopts/.yardopts: -------------------------------------------------------------------------------- 1 | foo/**/*.rb 2 | -------------------------------------------------------------------------------- /test/fixtures/ruby/yardopts/foo/bar.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | class Bar 3 | def initialize 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/integration/api/compare/codebases.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../test_helper') 2 | 3 | describe ::Inch::API::Compare::Codebases do 4 | let(:described_class) { ::Inch::API::Compare::Codebases } 5 | 6 | it 'should run' do 7 | codebase1 = Inch::Codebase.parse fixture_path(:ruby, :diff1) 8 | codebase2 = Inch::Codebase.parse fixture_path(:ruby, :diff2) 9 | 10 | compare = described_class.new(codebase1, codebase2) 11 | refute compare.comparisons.empty? 12 | 13 | # Foo#a is added in diff2 14 | comparison = compare.find('Foo#a') 15 | assert comparison.added? 16 | 17 | # Foo#b is improved in diff2 18 | comparison = compare.find('Foo#b') 19 | assert comparison.present? 20 | assert comparison.changed? 21 | assert comparison.improved? 22 | 23 | # Foo#c stayed the same 24 | comparison = compare.find('Foo#c') 25 | assert comparison.present? 26 | assert comparison.unchanged? 27 | refute comparison.changed? 28 | 29 | # Foo#d is removed in diff2 30 | comparison = compare.find('Foo#d') 31 | refute comparison.present? 32 | assert comparison.removed? 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/integration/cli/command/console_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../test_helper') 2 | 3 | class Inch::CLI::Command::Output::Console 4 | def run_pry 5 | nil 6 | end 7 | end 8 | 9 | describe ::Inch::CLI::Command::Console do 10 | before do 11 | Dir.chdir fixture_path(:ruby, :simple) 12 | @command = ::Inch::CLI::Command::Console 13 | end 14 | 15 | it 'should run with exit status' do 16 | _out, _err = capture_io do 17 | assert_equal @command.run.exit_status, @command::EXIT_STATUS_SUCCESS 18 | end 19 | end 20 | 21 | it 'should output info when run with --help' do 22 | out, err = capture_io do 23 | assert_raises(SystemExit) { @command.run('--help') } 24 | end 25 | refute out.empty?, 'there should be some output' 26 | assert_match(/\bUsage\b.+console/, out) 27 | assert err.empty?, 'there should be no errors' 28 | end 29 | 30 | it 'should run without args' do 31 | _out, _err = capture_io do 32 | @prompt = @command.new.run 33 | end 34 | assert @prompt.respond_to?(:all) 35 | assert @prompt.respond_to?(:ff) 36 | assert @prompt.respond_to?(:f) 37 | assert @prompt.respond_to?(:o) 38 | assert @prompt.o.nil? 39 | assert @prompt.objects.empty? 40 | end 41 | 42 | it 'should run with a definitive object name' do 43 | _out, _err = capture_io do 44 | @prompt = @command.new.run('Foo::Bar#method_with_full_doc') 45 | end 46 | assert !@prompt.all.empty? 47 | assert !@prompt.ff('Foo::Bar#').empty? 48 | assert !@prompt.f('Foo::Bar').nil? 49 | assert !@prompt.o.nil? 50 | refute @prompt.o.nil? 51 | assert_equal 1, @prompt.objects.size 52 | end 53 | 54 | it 'should run with a partial name' do 55 | _out, _err = capture_io do 56 | @prompt = @command.new.run('Foo::Bar#') 57 | end 58 | assert @prompt.respond_to?(:all) 59 | assert @prompt.respond_to?(:ff) 60 | assert @prompt.respond_to?(:f) 61 | assert @prompt.respond_to?(:o) 62 | refute @prompt.o.nil? 63 | assert @prompt.objects.size > 1 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/integration/cli/command/diff_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../test_helper') 2 | 3 | require 'tmpdir' 4 | require 'fileutils' 5 | 6 | describe ::Inch::CLI::Command::Diff do 7 | before do 8 | @command = ::Inch::CLI::Command::Diff 9 | @git_url = 'https://github.com/rrrene/sparkr.git' 10 | @git_dir = 'sparkr' 11 | @git_rev1 = '9da8aeaa64ff21daa1b39e3493134d42d67eb71a' 12 | @git_rev2 = 'HEAD' 13 | 14 | @tmp_dir = Dir.mktmpdir 15 | # clone the given repo to the tmp_dir 16 | Dir.chdir @tmp_dir 17 | `git clone #{@git_url} 2>&1` 18 | @cloned_dir = File.join(@tmp_dir, @git_dir) 19 | Dir.chdir @cloned_dir 20 | end 21 | 22 | after do 23 | FileUtils.rm_rf @tmp_dir 24 | end 25 | 26 | it 'should run with exit status' do 27 | _out, _err = capture_io do 28 | assert_equal @command.run.exit_status, @command::EXIT_STATUS_SUCCESS 29 | end 30 | end 31 | 32 | it 'should not show any changes' do 33 | # this runs `inch diff` on a freshly cloned repo 34 | # should not show any changes 35 | out, err = capture_io do 36 | @command.run 37 | end 38 | refute out.empty?, 'there should be some output' 39 | assert err.empty?, 'there should be no errors' 40 | assert_match(/\bno changes\b/i, out) 41 | 42 | # this runs `inch diff` on two distinct revisions 43 | # should show some changes 44 | out, err = capture_io do 45 | @command.run(@git_rev1, @git_rev2) 46 | end 47 | refute out.empty?, 'there should be some output' 48 | assert err.empty?, 'there should be no errors' 49 | assert_match(/\bshowing changes\b/i, out) 50 | 51 | # we now remove all comments in a single file 52 | filename = File.join(@cloned_dir, 'lib/sparkr.rb') 53 | content = File.read(filename) 54 | content_without_comments = content.gsub(/\s+#(.+)/, '') 55 | File.open(filename, 'w') { |f| f.write(content_without_comments) } 56 | 57 | # running the standard `inch diff` again 58 | # should now show some changes 59 | out, err = capture_io do 60 | @command.run 61 | end 62 | refute out.empty?, 'there should be some output' 63 | assert err.empty?, 'there should be no errors' 64 | assert_match(/\bshowing changes\b/i, out) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /test/integration/cli/command/stats_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../test_helper') 2 | 3 | describe ::Inch::CLI::Command::Stats do 4 | before do 5 | Dir.chdir fixture_path(:ruby, :simple) 6 | @command = ::Inch::CLI::Command::Stats 7 | end 8 | 9 | it 'should run with exit status' do 10 | _out, _err = capture_io do 11 | assert_equal @command.run.exit_status, @command::EXIT_STATUS_SUCCESS 12 | end 13 | end 14 | 15 | it 'should run without args' do 16 | out, err = capture_io do 17 | @command.run 18 | end 19 | refute out.empty?, 'there should be some output' 20 | assert err.empty?, 'there should be no errors' 21 | end 22 | 23 | it 'should run with filelist in args' do 24 | out, err = capture_io do 25 | @command.run('lib/**/*.rb', 'app/**/*.rb') 26 | end 27 | refute out.empty?, 'there should be some output' 28 | assert err.empty?, 'there should be no errors' 29 | end 30 | 31 | it 'should run even with non-existing filelist in args' do 32 | out, err = capture_io do 33 | @command.run('app/**/*.rb') 34 | end 35 | refute out.empty?, 'there should be some output' 36 | assert err.empty?, 'there should be no errors' 37 | end 38 | 39 | it 'should output info when run with --format=json' do 40 | out, err = capture_io do 41 | @command.run('--format=json') 42 | end 43 | refute out.empty?, 'there should be some output' 44 | assert err.empty?, "there should be no errors: #{err.color(:yellow)}" 45 | end 46 | 47 | it 'should output info when run with --format=yaml' do 48 | out, err = capture_io do 49 | @command.run('--format=yaml') 50 | end 51 | refute out.empty?, 'there should be some output' 52 | assert err.empty?, "there should be no errors: #{err.color(:yellow)}" 53 | end 54 | 55 | it 'should output info when run with --help' do 56 | out, err = capture_io do 57 | assert_raises(SystemExit) { @command.run('--help') } 58 | end 59 | refute out.empty?, 'there should be some output' 60 | assert_match(/\bUsage\b.+stats/, out) 61 | assert err.empty?, 'there should be no errors' 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/integration/stats_options_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../test_helper') 2 | 3 | # 4 | # These format tests also broke things in the regular testsuite. 5 | # See ./list_options_test.rb 6 | # 7 | # Therefore, here are some integration tests: 8 | # 9 | describe ::Inch::CLI::Command::List do 10 | before do 11 | Dir.chdir fixture_path(:ruby, :simple) 12 | @command = 'bundle exec inch stats' 13 | end 14 | 15 | def assert_parsed_output(parsed) 16 | assert parsed.size > 0 17 | assert parsed['grade_lists'] 18 | assert parsed['scores'] 19 | assert parsed['priorities'] 20 | end 21 | 22 | it 'should run with --no-private switch' do 23 | out = `#{@command} --format json` 24 | refute out.empty?, 'there should be some output' 25 | assert_parsed_output JSON[out] 26 | end 27 | 28 | it 'should run with --no-protected switch' do 29 | out = `#{@command} --format yaml` 30 | refute out.empty?, 'there should be some output' 31 | assert_parsed_output YAML.load(out) 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | 3 | require 'minitest/autorun' 4 | require 'bundler' 5 | Bundler.require 6 | require 'inch' 7 | require 'inch/cli' 8 | 9 | def assert_roles(object, expected, unexpected) 10 | roles = object.roles.map(&:class) 11 | unexpected.each do |role| 12 | refute roles.include?(role), "Should not assign #{role}" 13 | end 14 | expected.each do |role| 15 | assert roles.include?(role), "Should assign #{role}" 16 | end 17 | end 18 | 19 | def count_roles(object, role_class, object_name = nil) 20 | find_roles(object, role_class, object_name).size 21 | end 22 | 23 | def find_roles(object, role_class, object_name = nil) 24 | object.roles.select do |r| 25 | r.class == role_class && (object_name.nil? || r.object.name == object_name) 26 | end 27 | end 28 | 29 | def fixture_path(language, name) 30 | File.join(File.dirname(__FILE__), 'fixtures', language.to_s, name.to_s) 31 | end 32 | 33 | module Inch 34 | module Test 35 | class << self 36 | attr_accessor :object_providers 37 | 38 | def codebase(language, name) 39 | Inch::Codebase::Proxy.new language, object_provider(language, name) 40 | end 41 | 42 | def object_provider(language, name) 43 | self.object_providers ||= {} 44 | self.object_providers[name] ||= 45 | ::Inch::CodeObject::Provider.parse(fixture_path(language, name)) 46 | end 47 | end 48 | end 49 | end 50 | 51 | def test_codebase(language, name) 52 | codebase = Inch::Test.codebase(language, name) 53 | codebase 54 | end 55 | 56 | def fresh_codebase(language, name, read_dump_file = nil) 57 | dir = fixture_path(language, name) 58 | config = Inch::Config.for(language, dir).codebase 59 | config.read_dump_file = read_dump_file 60 | ::Inch::Codebase.parse(dir, config) 61 | end 62 | -------------------------------------------------------------------------------- /test/unit/api/filter_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::API::Filter do 4 | before do 5 | @codebase = test_codebase(:ruby, :simple) 6 | end 7 | 8 | it 'should work' do 9 | @options = {} 10 | @context = ::Inch::API::Filter.new @codebase, @options 11 | refute @context.objects.empty? 12 | refute @context.grade_lists.empty? 13 | end 14 | 15 | it 'should work with option: visibility == :public' do 16 | @options = { visibility: [:public] } 17 | @context = ::Inch::API::Filter.new @codebase, @options 18 | assert @context.objects.all? { |o| o.public? } 19 | end 20 | 21 | it 'should work with option: visibility == :protected' do 22 | @options = { visibility: [:protected] } 23 | @context = ::Inch::API::Filter.new @codebase, @options 24 | assert @context.objects.all? { |o| o.protected? } 25 | end 26 | 27 | it 'should work with option: visibility == :private' do 28 | @options = { visibility: [:private] } 29 | @context = ::Inch::API::Filter.new @codebase, @options 30 | assert @context.objects.all? { |o| o.private? || o.tagged_as_private? } 31 | end 32 | 33 | it 'should work with option: namespaces == :only' do 34 | @options = { namespaces: :only } 35 | @context = ::Inch::API::Filter.new @codebase, @options 36 | assert @context.objects.all? { |o| o.namespace? } 37 | end 38 | 39 | it 'should work with option: undocumented == :only' do 40 | @options = { undocumented: :only } 41 | @context = ::Inch::API::Filter.new @codebase, @options 42 | assert @context.objects.all? { |o| o.undocumented? } 43 | end 44 | 45 | it 'should work with option: depth == 2' do 46 | @options = { depth: 2 } 47 | @context = ::Inch::API::Filter.new @codebase, @options 48 | refute @context.objects.any? { |o| o.depth > 2 } 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/unit/api/get_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::API::Get do 4 | before do 5 | @codebase = test_codebase(:ruby, :simple) 6 | end 7 | 8 | it 'should work' do 9 | @object_names = ['Foo', 'Foo::Bar'] 10 | @context = ::Inch::API::Get.new @codebase, @object_names 11 | assert_equal 2, @context.objects.size 12 | refute @context.object.nil? 13 | refute @context.grade_lists.empty? 14 | end 15 | 16 | it 'should work with wildcard' do 17 | @object_names = ['Foo', 'Foo::Bar#'] 18 | @context = ::Inch::API::Get.new @codebase, @object_names 19 | assert @context.objects.size > 2 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/unit/api/list_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::API::List do 4 | before do 5 | @codebase = test_codebase(:ruby, :simple) 6 | end 7 | 8 | it 'should work' do 9 | @options = {} 10 | @context = ::Inch::API::List.new @codebase, @options 11 | refute @context.objects.empty? 12 | refute @context.grade_lists.empty? 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/unit/api/options/base_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../test_helper') 2 | 3 | describe ::Inch::API::Options::Base do 4 | class APIOptionsTest < ::Inch::API::Options::Base 5 | attribute :foo, :bar 6 | attribute :baz 7 | attribute :qux 8 | end 9 | 10 | it 'should work with a Hash or Struct' do 11 | @options_hash = { foo: 'foo', baz: 42 } 12 | @options_struct = OpenStruct.new(@options_hash) 13 | 14 | @options1 = APIOptionsTest.new @options_hash 15 | @options2 = APIOptionsTest.new @options_struct 16 | 17 | assert_equal 'foo', @options1.foo 18 | assert_equal 'foo', @options2.foo 19 | assert_equal 42, @options1.baz 20 | assert_equal 42, @options2.baz 21 | end 22 | 23 | it 'should return default values' do 24 | @options_hash = { baz: 42 } 25 | @options = APIOptionsTest.new @options_hash 26 | 27 | assert_equal :bar, @options.foo 28 | assert_equal 42, @options.baz 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/unit/api/stats_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::API::Stats do 4 | before do 5 | @codebase = test_codebase(:ruby, :simple) 6 | end 7 | 8 | it 'should work' do 9 | @options = {} 10 | @context = ::Inch::API::Stats.new @codebase, @options 11 | refute @context.objects.empty? 12 | refute @context.grade_lists.empty? 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/unit/api/suggest_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::API::Suggest do 4 | before do 5 | @codebase = test_codebase(:ruby, :simple) 6 | end 7 | 8 | it 'should work' do 9 | @options = {} 10 | @context = ::Inch::API::Suggest.new @codebase, @options 11 | refute @context.objects.empty? 12 | refute @context.grade_lists.empty? 13 | end 14 | 15 | it 'should work with option: object_count' do 16 | @options = { object_count: 10 } 17 | @context = ::Inch::API::Suggest.new @codebase, @options 18 | 19 | @options2 = { object_count: 20 } 20 | @context2 = ::Inch::API::Suggest.new @codebase, @options2 21 | 22 | assert_equal 10, @context.objects.size 23 | assert_equal 20, @context2.objects.size 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/unit/cli/arguments_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::CLI::Arguments do 4 | before do 5 | Dir.chdir fixture_path(:ruby, :simple) 6 | assert File.file?('README') 7 | end 8 | 9 | it 'should run with files' do 10 | args = [ 11 | 'lib/**/*.rb', 'app/**/*.rb', 'README' 12 | ] 13 | arguments = ::Inch::CLI::Arguments.new(args) 14 | assert_equal ['lib/**/*.rb', 'app/**/*.rb', 'README'], arguments.files 15 | assert_equal [], arguments.object_names 16 | assert_equal [], arguments.switches 17 | end 18 | 19 | it 'should run with directories as well' do 20 | args = [ 21 | 'lib', 'app/**/*.rb', 'README' 22 | ] 23 | arguments = ::Inch::CLI::Arguments.new(args) 24 | assert_equal ['lib', 'app/**/*.rb', 'README'], arguments.files 25 | assert_equal [], arguments.object_names 26 | assert_equal [], arguments.switches 27 | end 28 | 29 | it 'should run with files and object_name' do 30 | args = [ 31 | '{app,lib}.rb', 'README', 'Foo' 32 | ] 33 | arguments = ::Inch::CLI::Arguments.new(args) 34 | assert_equal ['{app,lib}.rb', 'README'], arguments.files 35 | assert_equal ['Foo'], arguments.object_names 36 | assert_equal [], arguments.switches 37 | end 38 | 39 | it 'should run with object_names' do 40 | args = [ 41 | 'Foo::Bar' 42 | ] 43 | arguments = ::Inch::CLI::Arguments.new(args) 44 | assert_equal [], arguments.files, 'files' 45 | assert_equal ['Foo::Bar'], arguments.object_names 46 | assert_equal [], arguments.switches 47 | end 48 | 49 | it 'should run with option switches' do 50 | args = [ 51 | '--no-color', '--all' 52 | ] 53 | arguments = ::Inch::CLI::Arguments.new(args) 54 | assert_equal [], arguments.files 55 | assert_equal [], arguments.object_names 56 | assert_equal ['--no-color', '--all'], arguments.switches 57 | end 58 | 59 | it 'should run with all of them' do 60 | args = [ 61 | 'lib/**/*.rb', 'app/**/*.rb', 'README', 62 | 'Foo', 'Foo::Bar', '--no-color', '--all' 63 | ] 64 | arguments = ::Inch::CLI::Arguments.new(args) 65 | assert_equal ['lib/**/*.rb', 'app/**/*.rb', 'README'], arguments.files 66 | assert_equal ['Foo', 'Foo::Bar'], arguments.object_names 67 | assert_equal ['--no-color', '--all'], arguments.switches 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /test/unit/cli/command/base_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../test_helper') 2 | 3 | describe ::Inch::CLI::Command::Console do 4 | before do 5 | @command = ::Inch::CLI::Command::Base.new 6 | end 7 | 8 | it 'should implement some defaults' do 9 | assert @command.name 10 | assert @command.usage 11 | assert @command.description 12 | 13 | assert_raises(NotImplementedError) { @command.run('something') } 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/unit/cli/command/options/base_list_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test_helper') 2 | 3 | describe ::Inch::CLI::Command::Options::BaseList do 4 | it 'should run parse without errors' do 5 | @options = ::Inch::CLI::Command::Options::BaseList.new 6 | @options.parse(['--no-color']) 7 | assert_equal false, @options.show_all? 8 | assert_equal [:public, :protected], @options.visibility 9 | assert @options.namespaces.nil? 10 | assert @options.undocumented.nil? 11 | assert @options.depth.nil? 12 | end 13 | 14 | it 'should run parse twice without affecting the second run' do 15 | @options = ::Inch::CLI::Command::Options::BaseList.new 16 | @options.parse(['--no-public', '--no-protected', '--private']) 17 | assert_equal [:private], @options.visibility 18 | 19 | @options = ::Inch::CLI::Command::Options::BaseList.new 20 | @options.parse(['--no-color']) 21 | assert_equal [:public, :protected], @options.visibility 22 | end 23 | 24 | it 'should interpret --all options' do 25 | @options = ::Inch::CLI::Command::Options::BaseList.new 26 | @options.parse(['--all']) 27 | assert_equal true, @options.show_all? 28 | end 29 | 30 | it 'should interpret visibility options' do 31 | @options = ::Inch::CLI::Command::Options::BaseList.new 32 | @options.parse(['--no-public', '--no-protected', '--private']) 33 | assert_equal [:private], @options.visibility 34 | end 35 | 36 | it 'should interpret options' do 37 | @options = ::Inch::CLI::Command::Options::BaseList.new 38 | @options.parse(['--no-namespaces', '--no-undocumented', '--depth=2']) 39 | assert_equal :none, @options.namespaces 40 | assert_equal :none, @options.undocumented 41 | assert_equal 2, @options.depth 42 | end 43 | 44 | it 'should interpret other options' do 45 | @options = ::Inch::CLI::Command::Options::BaseList.new 46 | @options.parse(['--only-namespaces', '--only-undocumented']) 47 | assert_equal :only, @options.namespaces 48 | assert_equal :only, @options.undocumented 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/unit/cli/command/options/base_object_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test_helper') 2 | 3 | describe ::Inch::CLI::Command::Options::BaseObject do 4 | it 'should run parse without errors' do 5 | @options = ::Inch::CLI::Command::Options::BaseObject.new 6 | @options.parse(['--no-color']) 7 | end 8 | 9 | it 'should interpret options' do 10 | @options = ::Inch::CLI::Command::Options::BaseObject.new 11 | @options.parse(['Foo', 'Foo::Bar', '--no-color']) 12 | assert_equal ['Foo', 'Foo::Bar'], @options.object_names 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/unit/cli/command/options/base_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test_helper') 2 | 3 | describe ::Inch::CLI::Command::Options::Base do 4 | it 'should run parse without errors' do 5 | @options = ::Inch::CLI::Command::Options::Base.new 6 | @options.parse(['--no-color']) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/unit/cli/trace_helper_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | class Tracer 4 | include ::Inch::CLI::TraceHelper 5 | end 6 | 7 | describe ::Inch::CLI::TraceHelper do 8 | it 'should be a UI instance' do 9 | @instance = Tracer.new 10 | assert @instance.ui.is_a?(Inch::Utils::UI) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/unit/code_object/converter_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::CodeObject::Converter do 4 | class MockObject 5 | def docstring 6 | 'Foo::Bar' 7 | end 8 | 9 | def parameters 10 | [] 11 | end 12 | 13 | def public? 14 | false 15 | end 16 | 17 | def private? 18 | true 19 | end 20 | end 21 | 22 | let(:object) { MockObject.new } 23 | it 'should parse all objects' do 24 | attributes = ::Inch::CodeObject::Converter.to_hash(object) 25 | assert_equal object.docstring, attributes[:docstring] 26 | assert_equal object.public?, attributes[:public?] 27 | assert_equal object.private?, attributes[:private?] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/unit/code_object/provider_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::CodeObject::Provider do 4 | it 'should parse all objects' do 5 | Dir.chdir File.dirname(__FILE__) 6 | @provider = ::Inch::CodeObject::Provider.parse(fixture_path(:ruby, :simple)) 7 | refute @provider.objects.empty? 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/unit/code_object/proxy_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::CodeObject::Proxy do 4 | before do 5 | @codebase = test_codebase(:ruby, :code_examples) 6 | @objects = @codebase.objects 7 | end 8 | 9 | def test_inspect_gives_original_name 10 | m = @objects.find('Foo::Bar#method_with_code_example') 11 | assert_match(/Foo::Bar#method_with_code_example/, m.inspect) 12 | end 13 | 14 | def test_grade_is_not_nil 15 | m = @objects.find('Foo::Bar#method_with_code_example') 16 | assert m.grade 17 | end 18 | 19 | def test_method_with_code_example 20 | m = @objects.find('Foo::Bar#method_with_code_example') 21 | assert m.has_code_example? 22 | end 23 | 24 | def test_method_with_code_example2 25 | m = @objects.find('Foo::Bar#method_with_code_example2') 26 | assert m.has_code_example? 27 | end 28 | 29 | def test_method_with_a_code_example 30 | m = @objects.find('Foo::Bar#method_with_one_example') 31 | assert m.has_code_example? 32 | refute m.has_multiple_code_examples? 33 | end 34 | 35 | def test_method_with_code_examples 36 | m = @objects.find('Foo::Bar#method_with_examples') 37 | assert m.has_multiple_code_examples? 38 | end 39 | 40 | def test_method_with_tagged_code_examples 41 | m = @objects.find('Foo::Bar#method_with_tagged_example') 42 | assert m.has_multiple_code_examples? 43 | end 44 | 45 | def test_method_with_two_tagged_code_examples 46 | m = @objects.find('Foo::Bar#method_with_2tagged_examples') 47 | assert m.has_multiple_code_examples? 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/unit/codebase/objects_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::Codebase::Objects do 4 | before do 5 | @codebase = test_codebase(:ruby, :simple) 6 | @objects = @codebase.objects 7 | end 8 | 9 | it 'should parse all objects' do 10 | refute @objects.empty? 11 | end 12 | 13 | it 'should find some objects' do 14 | refute_nil @objects.find('Foo') 15 | refute_nil @objects.find('Foo::Bar') 16 | refute_nil @objects.find('Foo::Bar#method_without_doc') 17 | end 18 | 19 | it 'should support iteration' do 20 | sum = 0 21 | @objects.each do 22 | sum += 1 23 | end 24 | assert_equal @objects.size, sum 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/unit/codebase/proxy_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::Codebase::Proxy do 4 | it 'should parse all objects' do 5 | dir = fixture_path(:ruby, :simple) 6 | config = Inch::Config::Codebase.new(:ruby, ['lib/**/*.rb']) 7 | config.object_provider :YARD 8 | @codebase = Inch::Codebase::Proxy.parse dir, config 9 | refute_nil @codebase.objects 10 | end 11 | 12 | it 'should parse given paths' do 13 | dir = fixture_path(:ruby, :simple) 14 | config = Inch::Config::Codebase.new(:ruby, ['app/**/*.rb']) 15 | config.object_provider :YARD 16 | @codebase = Inch::Codebase::Proxy.parse dir, config 17 | assert @codebase.objects.empty? 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/unit/config/codebase_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::Config::Codebase do 4 | it 'should parse .inch.yml' do 5 | dir = fixture_path(:ruby, :simple) 6 | config = Inch::Config::Codebase.new 7 | config.update_via_yaml(dir) 8 | assert config.included_files.empty? 9 | assert config.excluded_files.empty? 10 | end 11 | 12 | it 'should parse .inch.yml if present' do 13 | dir = fixture_path(:ruby, :"inch-yml") 14 | config = Inch::Config::Codebase.new 15 | config.update_via_yaml(dir) 16 | refute config.included_files.empty? 17 | refute config.excluded_files.empty? 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/unit/config_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../test_helper') 2 | 3 | describe ::Inch::Config do 4 | it 'should return config for :ruby' do 5 | config = Inch::Config.for(:ruby) 6 | refute config.codebase.included_files.empty? 7 | assert config.codebase.excluded_files.empty? 8 | end 9 | 10 | it 'should parse .inch.yml if present' do 11 | dir = fixture_path(:ruby, "inch-yml") 12 | config = Inch::Config.for(:ruby, dir) 13 | refute config.codebase.included_files.empty? 14 | refute config.codebase.excluded_files.empty? 15 | 16 | # Assert that this is another conf, unaltered 17 | # by the conf loading before 18 | config = Inch::Config.for(:ruby) 19 | refute config.codebase.included_files.empty? 20 | assert config.codebase.excluded_files.empty? 21 | end 22 | 23 | it 'should return config.evaluation for :ruby' do 24 | config = Inch::Config.for(:ruby) 25 | refute_nil config.evaluation.criteria_for(:MethodObject) 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /test/unit/evaluation/role_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | class MockPrivateRole < ::Inch::Evaluation::Role 4 | applicable_if :private? 5 | end 6 | 7 | class MockNotPrivateRole < ::Inch::Evaluation::Role 8 | applicable_unless :private? 9 | end 10 | 11 | class MockPublicRole < ::Inch::Evaluation::Role 12 | applicable_if { |o| o.public? } 13 | end 14 | 15 | class MockIndifferentRole < ::Inch::Evaluation::Role 16 | def self.applicable?(_object) 17 | true 18 | end 19 | end 20 | 21 | class MockPrivateObject 22 | def private? 23 | true 24 | end 25 | 26 | def public? 27 | false 28 | end 29 | end 30 | 31 | class MockPublicObject 32 | def private? 33 | false 34 | end 35 | 36 | def public? 37 | true 38 | end 39 | end 40 | 41 | describe ::Inch::Evaluation::Role do 42 | describe '.applicable' do 43 | let(:private_object) { MockPrivateObject.new } 44 | let(:public_object) { MockPublicObject.new } 45 | 46 | describe '.applicable_if' do 47 | it 'should work with a symbol' do 48 | assert MockPrivateRole.applicable?(private_object) 49 | assert !MockPrivateRole.applicable?(public_object) 50 | end 51 | 52 | it 'should work with a block' do 53 | assert MockPublicRole.applicable?(public_object) 54 | assert MockNotPrivateRole.applicable?(public_object) 55 | assert !MockPublicRole.applicable?(private_object) 56 | end 57 | end 58 | 59 | describe '.applicable_unless' do 60 | it 'should work with a block' do 61 | assert MockNotPrivateRole.applicable?(public_object) 62 | assert !MockNotPrivateRole.applicable?(private_object) 63 | end 64 | end 65 | 66 | it 'should work by implementing a class method' do 67 | assert MockIndifferentRole.applicable?(private_object) 68 | assert MockIndifferentRole.applicable?(public_object) 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /test/unit/language/elixir/code_object/callback_object_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test_helper') 2 | 3 | describe ::Inch::Language::Elixir::CodeObject::FunctionObject do 4 | before do 5 | @codebase = fresh_codebase(:elixir, :inch_test, 'all.json') 6 | @objects = @codebase.objects 7 | end 8 | 9 | describe 'Scores' do 10 | # 11 | it 'should not' do 12 | m = @objects.find('InchTest.Callbacks.full_doc/1') 13 | assert m.score >= 50 14 | end 15 | # 16 | it 'should recognize @doc false' do 17 | m = @objects.find('InchTest.Callbacks.no_doc/1') 18 | assert m.nodoc? 19 | end 20 | # 21 | it 'should not' do 22 | m = @objects.find('InchTest.Callbacks.missing_doc/1') 23 | assert m.score == 0 24 | assert m.undocumented? 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/unit/language/elixir/code_object/macro_object_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test_helper') 2 | 3 | describe ::Inch::Language::Elixir::CodeObject::FunctionObject do 4 | before do 5 | @codebase = fresh_codebase(:elixir, :inch_test, 'all.json') 6 | @objects = @codebase.objects 7 | end 8 | 9 | describe 'Scores' do 10 | # 11 | it 'should not' do 12 | m = @objects.find('InchTest.Macros.full_doc/0') 13 | assert m.score >= 50 14 | end 15 | # 16 | it 'should recognize @doc false' do 17 | m = @objects.find('InchTest.Macros.no_doc/0') 18 | assert m.nodoc? 19 | end 20 | # 21 | it 'should not' do 22 | m = @objects.find('InchTest.Macros.missing_doc/0') 23 | assert m.score == 0 24 | assert m.undocumented? 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/unit/language/elixir/code_object/module_object_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test_helper') 2 | 3 | describe ::Inch::Language::Elixir::CodeObject::ModuleObject do 4 | before do 5 | @codebase = fresh_codebase(:elixir, :inch_test, 'all.json') 6 | @objects = @codebase.objects 7 | end 8 | 9 | describe 'Scores' do 10 | # 11 | it 'should not' do 12 | m = @objects.find('InchTest.Functions') 13 | refute m.docstring.empty? 14 | refute m.undocumented? 15 | assert m.score >= 50 16 | end 17 | end 18 | 19 | it 'should recognize moduledoc' do 20 | m = @objects.find('InchTest.Invisible') 21 | # defined in Hello-World-Elixir, but not in report since 22 | # `@moduledoc false` excludes it 23 | assert m.nil? 24 | end 25 | 26 | it 'should recognize moduledoc' do 27 | m = @objects.find('InchTest.Invisible.foo') 28 | # defined in Hello-World-Elixir, but not in report since 29 | # `@moduledoc false` excludes it 30 | assert m.nil? 31 | end 32 | 33 | it 'should recognize protocols' do 34 | m = @objects.find('InchTest.Html.Safe') 35 | refute m.nodoc? 36 | end 37 | 38 | it 'should recognize implementations' do 39 | m = @objects.find('InchTest.Html.Safe.Atom') 40 | assert m.nodoc? 41 | assert m.priority < 0 42 | end 43 | 44 | it 'should recognize code location' do 45 | m = @objects.find('InchTest.Functions') 46 | file = m.filename 47 | refute_nil file 48 | assert_equal 'lib/inch_test/functions.ex', file.relative_path 49 | assert_equal 1, file.line_no.to_i 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/unit/language/ruby/code_object/alias_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test_helper') 2 | 3 | describe ::Inch::Language::Ruby::CodeObject::MethodObject do 4 | before do 5 | @codebase = test_codebase(:ruby, :alias_cycle) 6 | @objects = @codebase.objects 7 | end 8 | 9 | describe 'Parser' do 10 | # 11 | it 'should not raise stack level too deep' do 12 | m = @objects.find('ViciousAliasCycle') 13 | m.score 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /test/unit/language/ruby/code_object/structs_member_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test_helper') 2 | 3 | describe ::Inch::Language::Ruby::CodeObject::ClassObject do 4 | before do 5 | @codebase = test_codebase(:ruby, :structs) 6 | @objects = @codebase.objects 7 | end 8 | 9 | describe 'Structs with RDoc comment mentioning members' do 10 | # 11 | it 'should recognize that the members are documented in the class docstring' do 12 | %w(StructWithRDoc StructWithRDocAsInheritedClass).each do |class_name| 13 | %w(type oid gen pos objstm).each do |member_name| 14 | m = @objects.find("#{class_name}##{member_name}") 15 | refute_equal 0, m.score, "#{class_name}##{member_name} should have score > 0" 16 | refute m.undocumented?, "#{class_name}##{member_name} should not be undocumented" 17 | end 18 | m = @objects.find("#{class_name}#member_without_doc") 19 | assert_equal 0, m.score, "#{class_name}#member_without_doc should have score == 0" 20 | assert m.undocumented?, "#{class_name}#member_without_doc should be undocumented" 21 | end 22 | end 23 | end 24 | 25 | describe 'Structs with YARD directives' do 26 | # 27 | it 'should recognize that the members are documented via directives' do 28 | %w(email username).each do |member_name| 29 | m = @objects.find("StructWithYardDirectivesAsInheritedClass##{member_name}") 30 | refute_equal 0, m.score, "StructWithYardDirectivesAsInheritedClass##{member_name} should have score > 0" 31 | refute m.undocumented?, "StructWithYardDirectivesAsInheritedClass##{member_name} should not be undocumented" 32 | end 33 | m = @objects.find("StructWithYardDirectivesAsInheritedClass#member_without_doc") 34 | assert_equal 0, m.score, "StructWithYardDirectivesAsInheritedClass#member_without_doc should have score == 0" 35 | assert m.undocumented?, "StructWithYardDirectivesAsInheritedClass#member_without_doc should be undocumented" 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/unit/language/ruby/provider/yard/nodoc_helper_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../../test_helper') 2 | 3 | describe ::Inch::Language::Ruby::Provider::YARD::NodocHelper do 4 | before do 5 | @config = Inch::Config.codebase 6 | @provider = ::Inch::Language::Ruby::Provider::YARD.parse( 7 | fixture_path(:ruby, :simple), @config) 8 | @objects = @provider.objects 9 | end 10 | 11 | it 'should return true for explicitly or implicitly tagged objects' do 12 | [ 13 | 'Foo::Qux', 14 | 'Foo::Qux#method_with_implicit_nodoc', 15 | 'Foo::Qux::Quux#method_with_private_tag', 16 | 'Foo::Qux::Quux#method_with_explicit_nodoc', 17 | 'Foo::Qux::Quux::PRIVATE_VALUE', 18 | 'Foo::HiddenClass', 19 | 'Foo::HiddenClass::EvenMoreHiddenClass', 20 | 'Foo::HiddenClass::EvenMoreHiddenClass#method_with_implicit_nodoc', 21 | 'Foo::HiddenClassViaTag', 22 | 'Foo::HiddenClassViaTag#some_value' 23 | ].each do |query| 24 | m = @objects.find { |o| o.fullname == query } 25 | assert m.nodoc?, "nodoc? should return true for #{query}" 26 | end 27 | end 28 | 29 | it 'should return false for other objects' do 30 | [ 31 | 'Foo::Qux::Quux#method_without_nodoc', 32 | 'Foo::Qux::Quux::PUBLIC_VALUE', 33 | 'Foo::Qux::DOCCED_VALUE', 34 | 'Foo::HiddenClass::EvenMoreHiddenClass::SuddenlyVisibleClass', 35 | 'Foo::HiddenClass::EvenMoreHiddenClass::SuddenlyVisibleClass' \ 36 | '#method_with_implicit_doc' 37 | ].each do |query| 38 | m = @objects.find { |o| o.fullname == query } 39 | refute m.nodoc?, "nodoc? should return false for #{query}" 40 | end 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /test/unit/language/ruby/provider/yard_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test_helper') 2 | 3 | describe ::Inch::Language::Ruby::Provider::YARD do 4 | let(:described_class) { ::Inch::Language::Ruby::Provider::YARD } 5 | let(:config) { ::Inch::Config.codebase } 6 | 7 | it 'should parse' do 8 | provider = described_class.parse(fixture_path(:ruby, :simple), config) 9 | assert !provider.objects.empty? 10 | end 11 | 12 | it 'should parse too different codebases' do 13 | fullname = 'Foo#b' 14 | 15 | provider1 = described_class.parse(fixture_path(:ruby, :diff1), config) 16 | object1 = provider1.objects.find { |o| o.fullname == fullname } 17 | 18 | provider2 = described_class.parse(fixture_path(:ruby, :diff2), config) 19 | object2 = provider2.objects.find { |o| o.fullname == fullname } 20 | 21 | refute object1.nil? 22 | refute object2.nil? 23 | assert object1.object_id != object2.object_id 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/unit/utils/buffered_ui_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | require 'inch/utils/buffered_ui' 3 | 4 | describe ::Inch::Utils::BufferedUI do 5 | let(:described_class) { ::Inch::Utils::BufferedUI } 6 | it 'should trace' do 7 | out, err = capture_io do 8 | @instance = described_class.new 9 | @instance.trace('Test') 10 | end 11 | assert out.empty?, 'there should be output' 12 | assert err.empty?, 'there should be no errors' 13 | refute @instance.buffer.empty? 14 | end 15 | 16 | it 'should trace header' do 17 | out, err = capture_io do 18 | @instance = described_class.new 19 | @instance.header('Test', :red) 20 | end 21 | assert out.empty?, 'there should be output' 22 | assert err.empty?, 'there should be no errors' 23 | refute @instance.buffer.empty? 24 | end 25 | 26 | it 'should trace debug if ENV variable is set' do 27 | ENV['DEBUG'] = '1' 28 | out, err = capture_io do 29 | @instance = described_class.new 30 | @instance.debug('Test') 31 | end 32 | ENV['DEBUG'] = nil 33 | assert out.empty?, 'there should be output' 34 | assert err.empty?, 'there should be no errors' 35 | refute @instance.buffer.empty? 36 | end 37 | 38 | it 'should not trace debug if ENV variable is set' do 39 | refute ENV['DEBUG'] 40 | out, err = capture_io do 41 | @instance = described_class.new 42 | @instance.debug('Test') 43 | end 44 | assert out.empty?, 'there should be no output' 45 | assert err.empty?, 'there should be no errors' 46 | assert @instance.buffer.empty? 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/unit/utils/ui_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::Utils::UI do 4 | it 'should trace' do 5 | out, err = capture_io do 6 | @instance = ::Inch::Utils::UI.new 7 | @instance.trace('Test') 8 | end 9 | refute out.empty?, 'there should be output' 10 | assert err.empty?, 'there should be no errors' 11 | end 12 | 13 | it 'should trace header' do 14 | out, err = capture_io do 15 | @instance = ::Inch::Utils::UI.new 16 | @instance.header('Test', :red) 17 | end 18 | refute out.empty?, 'there should be output' 19 | assert err.empty?, 'there should be no errors' 20 | end 21 | 22 | it 'should trace debug if ENV variable is set' do 23 | ENV['DEBUG'] = '1' 24 | out, err = capture_io do 25 | @instance = ::Inch::Utils::UI.new 26 | @instance.debug('Test') 27 | end 28 | ENV['DEBUG'] = nil 29 | refute out.empty?, 'there should be output' 30 | assert err.empty?, 'there should be no errors' 31 | end 32 | 33 | it 'should not trace debug if ENV variable is set' do 34 | refute ENV['DEBUG'] 35 | out, err = capture_io do 36 | @instance = ::Inch::Utils::UI.new 37 | @instance.debug('Test') 38 | end 39 | assert out.empty?, 'there should be no output' 40 | assert err.empty?, 'there should be no errors' 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/unit/utils/weighted_list_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../test_helper') 2 | 3 | describe ::Inch::Utils::WeightedList do 4 | before do 5 | @counts = [4, 8, 8] 6 | end 7 | 8 | def assert_weighted_list(list, counts, expected) 9 | weighted_list = ::Inch::Utils::WeightedList.new(list, counts) 10 | # assert_equal expected.map(&:size).inject(:+), 11 | # weighted_list.to_a.map(&:size).inject(:+) 12 | assert_equal expected, weighted_list.to_a, 13 | "should be #{expected.map(&:size)}, was " \ 14 | "#{weighted_list.to_a.map(&:size)}" 15 | end 16 | 17 | def list_and_expected(counts, expected_counts) 18 | elements = [:B, :C, :U] 19 | list = counts.map.with_index do |num, index| 20 | (1..num).map { |i| :"#{elements[index]}#{i}" } 21 | end 22 | expected = expected_counts.map.with_index do |num, index| 23 | (1..num).map { |i| :"#{elements[index]}#{i}" } 24 | end 25 | [list, expected] 26 | end 27 | 28 | it 'should work if elements are exact' do 29 | @list, @expected = list_and_expected([4, 8, 8], [4, 8, 8]) 30 | assert_weighted_list(@list, @counts, @expected) 31 | end 32 | 33 | it 'should work if more than enough elements are present' do 34 | @list, @expected = list_and_expected([10, 10, 10], [4, 8, 8]) 35 | assert_weighted_list(@list, @counts, @expected) 36 | end 37 | 38 | it 'should work if not enough Bs are present' do 39 | @list, @expected = list_and_expected([2, 12, 15], [2, 8, 10]) 40 | assert_weighted_list(@list, @counts, @expected) 41 | end 42 | 43 | it 'should work if not enough Cs are present' do 44 | @list, @expected = list_and_expected([15, 4, 15], [4, 4, 12]) 45 | assert_weighted_list(@list, @counts, @expected) 46 | end 47 | 48 | it 'should work if not enough Us are present' do 49 | @list, @expected = list_and_expected([15, 15, 4], [4, 12, 4]) 50 | assert_weighted_list(@list, @counts, @expected) 51 | end 52 | 53 | it 'should work if not enough Bs AND Cs and Us are present' do 54 | @list, @expected = list_and_expected([2, 2, 15], [2, 2, 15]) 55 | assert_weighted_list(@list, @counts, @expected) 56 | end 57 | 58 | end 59 | --------------------------------------------------------------------------------