├── .gitignore
├── .rspec
├── .rubocop.yml
├── .travis.yml
├── Gemfile
├── LICENSE.md
├── README.md
├── Rakefile
├── bin
└── phare
├── lib
├── phare.rb
└── phare
│ ├── check.rb
│ ├── check
│ ├── eslint.rb
│ ├── rubocop.rb
│ └── stylelint.rb
│ ├── check_suite.rb
│ ├── cli.rb
│ ├── git.rb
│ └── version.rb
├── phare.gemspec
└── spec
├── phare
├── check
│ ├── eslint_spec.rb
│ ├── rubocop_spec.rb
│ └── stylelint_spec.rb
├── check_suite_spec.rb
├── cli_spec.rb
└── git_spec.rb
└── spec_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | Include:
3 | - phare.gemspec
4 |
5 | Documentation:
6 | Enabled: false
7 |
8 | Encoding:
9 | Enabled: false
10 |
11 | LineLength:
12 | Max: 200
13 |
14 | ClassLength:
15 | Max: 200
16 |
17 | AccessModifierIndentation:
18 | EnforcedStyle: outdent
19 |
20 | IfUnlessModifier:
21 | Enabled: false
22 |
23 | CaseIndentation:
24 | IndentWhenRelativeTo: case
25 | IndentOneStep: true
26 |
27 | MethodLength:
28 | CountComments: false
29 | Max: 20
30 |
31 | SignalException:
32 | Enabled: false
33 |
34 | ColonMethodCall:
35 | Enabled: false
36 |
37 | AsciiComments:
38 | Enabled: false
39 |
40 | Lambda:
41 | Enabled: false
42 |
43 | RegexpLiteral:
44 | Enabled: false
45 |
46 | AssignmentInCondition:
47 | Enabled: false
48 |
49 | ClassAndModuleChildren:
50 | Enabled: false
51 |
52 | GuardClause:
53 | Enabled: false
54 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | rvm:
4 | - 2.4.2
5 | - 2.3
6 | - 2.2
7 | - 2.1
8 | - 2.0
9 |
10 | script:
11 | - 'echo "Checking code style" && ./bin/phare'
12 | - 'echo "Running specs" && bundle exec rake spec'
13 |
14 | notifications:
15 | hipchat:
16 | rooms:
17 | secure: "OjYHtwmsG6bDC8mewX85yulCKxsVpvGiic32e6lDRq2TYmMtot/atOQaZ16YlMMN0CQqiJ8jfpil3HWCSK1iPZxSLHzCEu//bL6uoDJdPc/oXNV2BPAj0CxyHiJPpV3gdgdnZBbYGNdDPtjxo8FuZnM+cWIDl3yW0Y2rQkE7u0k="
18 | template:
19 | - '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message} (Build/Changes)'
20 | format: 'html'
21 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gemspec
3 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-2015, Mirego
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | - Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 | - Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | - Neither the name of the Mirego nor the names of its contributors may
13 | be used to endorse or promote products derived from this software without
14 | specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Phare looks into your files and check for coding style errors.
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Installation
14 |
15 | Add these lines to your application’s `Gemfile` as development dependencies:
16 |
17 | ```ruby
18 | group :development do
19 | gem 'rubocop' # to check Ruby code
20 | gem 'scss-lint' # to check SCSS code
21 |
22 | gem 'phare'
23 | end
24 | ```
25 |
26 | ```shell
27 | $ bundle install
28 | ```
29 |
30 | If you wish to check for JavaScript code style using JSHint and JSCS, you must
31 | specify them in your `package.json` file:
32 |
33 | ```json
34 | {
35 | "name": "foo",
36 | "version": "0.0.1",
37 | "devDependencies": {
38 | "jshint": "latest",
39 | "jscs": "latest"
40 | }
41 | }
42 | ```
43 |
44 | ```shell
45 | $ npm install
46 | ```
47 |
48 | ### Shims
49 |
50 | Phare uses top-level commands in its checks (eg. `$ rubocop` and not `$ bundle exec rubocop`).
51 | You’ll need to run these commands in order to use the shims provided by either
52 | Bundler or NPM.
53 |
54 | #### Bundler
55 |
56 | ```shell
57 | $ bundle install
58 | $ bundle binstub rubocop scss-lint
59 | $ export PATH="./bin:$PATH"
60 | ```
61 |
62 | #### npm
63 |
64 | ```shell
65 | $ npm install
66 | $ export PATH="./node_modules/.bin:$PATH"
67 | ```
68 |
69 | ## Usage
70 |
71 | Phare provides an executable named `phare`. You can just use it as is:
72 |
73 | ```bash
74 | $ phare
75 | ```
76 |
77 | ### Version control hook
78 |
79 | One of the best ways to use Phare is by hooking it to your version control
80 | commit process. For example, with `git`:
81 |
82 | ```bash
83 | $ bundle binstubs phare
84 | $ ln -s "`pwd`/bin/phare" .git/hooks/pre-commit
85 | ```
86 |
87 | That way, every time `git commit` is ran, `phare` will be executed and the
88 | commit will be aborted if there are some errors. However, you can skip this
89 | check altogether by specifying `SKIP_PHARE=1` before your command.
90 |
91 | ```bash
92 | $ git commit -m 'Add stuff'
93 | $ SKIP_PHARE=1 git commit -m 'Add stuff and I don’t care about Phare'
94 | ```
95 |
96 | ### Options
97 |
98 | #### Command-line
99 |
100 | | Option | Description
101 | |-------------|-------------------------------------------------------------------------------------------------------------------------
102 | | `directory` | The directory in which to run the checks (default is the current directory
103 | | `only` | The specific checks to run (e.g. `--only=rubocop,jscs`)
104 | | `skip` | The checks to skip (e.g. `--skip=scsslint`)
105 |
106 | #### `.phare.yml`
107 |
108 | Instead of using command-line arguments when running the `phare` command, you
109 | can create a `.phare.yml` file at the root of your project and hard-code options
110 | in that file.
111 |
112 | ```yaml
113 | skip:
114 | - scsslint
115 | - jshint
116 | ```
117 |
118 | ## Contributors
119 |
120 | * [@remiprev](https://github.com/remiprev)
121 | * [@garno](https://github.com/garno)
122 |
123 | ## License
124 |
125 | `Phare` is © 2014-2015 [Mirego](http://www.mirego.com) and may be freely distributed under the [New BSD license](http://opensource.org/licenses/BSD-3-Clause). See the [`LICENSE.md`](https://github.com/mirego/phare/blob/master/LICENSE.md) file.
126 |
127 | The lighthouse logo is based on [this lovely icon](http://thenounproject.com/term/lighthouse/11608/) by [Nick Lacke](http://thenounproject.com/nicklacke), from The Noun Project.
128 |
129 | ## About Mirego
130 |
131 | [Mirego](http://mirego.com) is a team of passionate people who believe that work is a place where you can innovate and have fun. We're a team of [talented people](http://life.mirego.com) who imagine and build beautiful Web and mobile applications. We come together to share ideas and [change the world](http://mirego.org).
132 |
133 | We also [love open-source software](http://open.mirego.com) and we try to give back to the community as much as we can.
134 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler'
2 | require 'rake'
3 | require 'bundler/gem_tasks'
4 | require 'rspec/core/rake_task'
5 |
6 | task default: :spec
7 |
8 | desc 'Run all specs'
9 | RSpec::Core::RakeTask.new(:spec) do |task|
10 | task.pattern = 'spec/**/*_spec.rb'
11 | end
12 |
--------------------------------------------------------------------------------
/bin/phare:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | $LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w(.. lib))
4 |
5 | require 'phare'
6 |
7 | Phare::CLI.new(ENV, ARGV).run
8 |
--------------------------------------------------------------------------------
/lib/phare.rb:
--------------------------------------------------------------------------------
1 | require 'English'
2 | require 'optparse'
3 | require 'yaml'
4 | require 'phare/version'
5 |
6 | require 'phare/cli'
7 | require 'phare/git'
8 |
9 | require 'phare/check'
10 | require 'phare/check/rubocop'
11 | require 'phare/check/stylelint'
12 | require 'phare/check/eslint'
13 |
14 | require 'phare/check_suite'
15 |
16 | module Phare
17 | def self.system(*args)
18 | Kernel.system(*args)
19 | end
20 |
21 | def self.system_output(args)
22 | `#{args}`
23 | end
24 |
25 | def self.last_exit_status
26 | $CHILD_STATUS.exitstatus
27 | end
28 |
29 | def self.puts(*args)
30 | STDOUT.puts(*args)
31 | end
32 |
33 | def self.banner(string)
34 | Phare.puts '-' * string.length
35 | Phare.puts string
36 | Phare.puts '-' * string.length
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/phare/check.rb:
--------------------------------------------------------------------------------
1 | module Phare
2 | class Check
3 | attr_reader :status, :command, :tree
4 |
5 | def initialize(_directory, options = {})
6 | @tree = Git.new(@extensions, options)
7 | end
8 |
9 | def run
10 | if should_run?
11 | print_banner
12 | Phare.system(command)
13 | @status = Phare.last_exit_status
14 |
15 | if @status == 0
16 | print_success_message
17 | else
18 | print_error_message
19 | end
20 |
21 | Phare.puts ''
22 | else
23 | @status = 0
24 | end
25 | end
26 |
27 | def should_run?
28 | should_run = binary_exists?
29 |
30 | [:configuration_exists?, :arguments_exists?].each do |condition|
31 | should_run &&= send(condition) if respond_to?(condition, true)
32 | end
33 |
34 | if @options[:diff]
35 | # NOTE: If the tree hasn't changed or if there is no files
36 | # to check (e.g. they are all in the exclude list),
37 | # we skip the check.
38 | should_run &&= @tree.changed? && files_to_check.any?
39 | end
40 |
41 | should_run
42 | end
43 |
44 | protected
45 |
46 | def excluded_files
47 | @excluded_files ||= [Dir.glob(excluded_list || [])].flatten
48 | end
49 |
50 | def excluded_list
51 | [] # Should be overriden by children
52 | end
53 |
54 | def files_to_check
55 | @files_to_check ||= @tree.changes - excluded_files
56 | end
57 |
58 | def print_success_message
59 | Phare.puts('Everything looks good from here!')
60 | end
61 |
62 | def print_error_message
63 | Phare.puts("Something went wrong. Program exited with #{@status}.")
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/phare/check/eslint.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module Phare
3 | class Check
4 | class Eslint < Check
5 | GLOBAL_BINARY = 'eslint'.freeze
6 | LOCAL_BINARY = 'node_modules/.bin/eslint'.freeze
7 |
8 | attr_reader :config, :path
9 |
10 | def initialize(directory, options = {})
11 | @directory = directory
12 | @config = File.expand_path("#{directory}.eslintrc", __FILE__)
13 | @path = File.expand_path("#{directory}app/assets/javascripts", __FILE__)
14 | @extensions = %w(.js)
15 | @options = options
16 |
17 | super
18 | end
19 |
20 | def command
21 | "#{binary} #{input}"
22 | end
23 |
24 | protected
25 |
26 | def configuration_exists?
27 | File.exist?(@config)
28 | end
29 |
30 | def binary
31 | local_binary_exists? ? @directory + LOCAL_BINARY : GLOBAL_BINARY
32 | end
33 |
34 | def binary_exists?
35 | local_binary_exists? || global_binary_exists?
36 | end
37 |
38 | def local_binary_exists?
39 | !Phare.system_output("which #{@directory}#{LOCAL_BINARY}").empty?
40 | end
41 |
42 | def global_binary_exists?
43 | !Phare.system_output("which #{GLOBAL_BINARY}").empty?
44 | end
45 |
46 | def input
47 | @tree.changed? ? files_to_check.join(' ') : "'#{@path}/**/*#{@extensions.first}'"
48 | end
49 |
50 | def arguments_exists?
51 | @tree.changed? || Dir.exist?(@path)
52 | end
53 |
54 | def print_banner
55 | Phare.banner 'Running ESLint to check for JavaScript style…'
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/phare/check/rubocop.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module Phare
3 | class Check
4 | class Rubocop < Check
5 | def initialize(directory, options = {})
6 | @path = directory
7 | @extensions = %w(.rb)
8 | @options = options
9 |
10 | super
11 | end
12 |
13 | def command
14 | if @tree.changed?
15 | "rubocop #{files_to_check.join(' ')}"
16 | else
17 | 'rubocop'
18 | end
19 | end
20 |
21 | protected
22 |
23 | def excluded_list
24 | configuration_file['AllCops']['Exclude'] if configuration_file['AllCops'] && configuration_file['AllCops']['Exclude']
25 | end
26 |
27 | def configuration_file
28 | @configuration_file ||= File.exist?('.rubocop.yml') ? YAML::load(File.open('.rubocop.yml')) : {}
29 | end
30 |
31 | def binary_exists?
32 | !Phare.system_output('which rubocop').empty?
33 | end
34 |
35 | def print_banner
36 | Phare.banner 'Running Rubocop to check for Ruby style…'
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/phare/check/stylelint.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module Phare
3 | class Check
4 | class Stylelint < Check
5 | GLOBAL_BINARY = 'stylelint'.freeze
6 | LOCAL_BINARY = 'node_modules/.bin/stylelint'.freeze
7 |
8 | attr_reader :config, :path
9 |
10 | def initialize(directory, options = {})
11 | @directory = directory
12 | @config = File.expand_path("#{directory}.stylelintrc", __FILE__)
13 | @path = File.expand_path("#{directory}app/assets/stylesheets", __FILE__)
14 | @extensions = %w(.scss)
15 | @options = options
16 |
17 | super
18 | end
19 |
20 | def command
21 | "#{binary} #{input}"
22 | end
23 |
24 | protected
25 |
26 | def configuration_exists?
27 | File.exist?(@config)
28 | end
29 |
30 | def binary
31 | local_binary_exists? ? @directory + LOCAL_BINARY : GLOBAL_BINARY
32 | end
33 |
34 | def binary_exists?
35 | local_binary_exists? || global_binary_exists?
36 | end
37 |
38 | def local_binary_exists?
39 | !Phare.system_output("which #{@directory}#{LOCAL_BINARY}").empty?
40 | end
41 |
42 | def global_binary_exists?
43 | !Phare.system_output("which #{GLOBAL_BINARY}").empty?
44 | end
45 |
46 | def input
47 | @tree.changed? ? files_to_check.join(' ') : "'#{@path}/**/*#{@extensions.first}'"
48 | end
49 |
50 | def arguments_exists?
51 | @tree.changed? || Dir.exist?(@path)
52 | end
53 |
54 | def print_banner
55 | Phare.banner 'Running Stylelint to check for SCSS style…'
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/phare/check_suite.rb:
--------------------------------------------------------------------------------
1 | module Phare
2 | class CheckSuite
3 | attr_reader :status
4 |
5 | DEFAULT_CHECKS = {
6 | rubocop: Check::Rubocop,
7 | stylelint: Check::Stylelint,
8 | eslint: Check::Eslint
9 | }
10 |
11 | def initialize(options = {})
12 | @options = options
13 |
14 | @directory = options[:directory]
15 | @directory << '/' unless @directory.end_with?('/')
16 |
17 | @options[:skip] ||= []
18 | @options[:only] ||= []
19 | end
20 |
21 | def checks
22 | checks = DEFAULT_CHECKS.keys
23 |
24 | if @options[:only].any?
25 | checks &= @options[:only]
26 | elsif @options[:skip]
27 | checks - @options[:skip]
28 | else
29 | checks
30 | end
31 | end
32 |
33 | def run
34 | @checks = checks.map do |check|
35 | check = DEFAULT_CHECKS[check]
36 | check.new(@directory, @options).tap(&:run).status
37 | end
38 |
39 | @status = @checks.find { |status| status > 0 } || 0
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/phare/cli.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module Phare
3 | class CLI
4 | attr_reader :suite
5 |
6 | def initialize(env, argv)
7 | @env = env
8 | @options = parsed_options(argv)
9 |
10 | @suite = Phare::CheckSuite.new(@options)
11 | end
12 |
13 | def run
14 | if @options[:version]
15 | Phare.puts Phare::VERSION
16 | exit 0
17 | elsif @env['SKIP_CODE_CHECK'] || @env['SKIP_PHARE']
18 | Phare.banner 'Skipping code style checking… Really? Well alright then…'
19 | exit 0
20 | else
21 | exit run_suite
22 | end
23 | end
24 |
25 | protected
26 |
27 | def parsed_options(argv)
28 | options = { directory: Dir.getwd }
29 | options.merge! parsed_options_from_yaml(File.join(options[:directory], '.phare.yml'))
30 | options.merge! parsed_options_from_arguments(argv)
31 | symbolize_options!(options)
32 |
33 | options
34 | end
35 |
36 | def symbolize_options!(options)
37 | options[:skip].map!(&:to_sym) if options[:skip]
38 | options[:only].map!(&:to_sym) if options[:only]
39 |
40 | options
41 | end
42 |
43 | # rubocop:disable Metrics/AbcSize
44 | def parsed_options_from_arguments(argv)
45 | options_to_merge = {}
46 |
47 | OptionParser.new do |opts|
48 | opts.banner = 'Usage: phare [options]'
49 |
50 | opts.on('--version', 'Display Phare’s version') do
51 | options_to_merge[:version] = true
52 | end
53 |
54 | opts.on('--directory x', 'The directory in which to run the checks (default is the current directory') do |directory|
55 | options_to_merge[:directory] = directory
56 | end
57 |
58 | opts.on('--skip x,y,z', 'Skip checks') do |checks|
59 | options_to_merge[:skip] = checks.split(',')
60 | end
61 |
62 | opts.on('--only x,y,z', 'Only run the specified checks') do |checks|
63 | options_to_merge[:only] = checks.split(',')
64 | end
65 |
66 | opts.on('--diff', 'Only run checks on modified files') do
67 | options_to_merge[:diff] = true
68 | end
69 | end.parse! argv
70 |
71 | options_to_merge
72 | end
73 | # rubocop:enable Metrics/AbcSize
74 |
75 | def parsed_options_from_yaml(file)
76 | options_to_merge = {}
77 |
78 | if File.exist?(file)
79 | # Load YAML content
80 | content = YAML.load(File.read(file))
81 |
82 | # Symbolize keys
83 | options_to_merge = content.reduce({}) do |memo, (key, value)|
84 | memo.merge! key.to_sym => value
85 | end
86 | end
87 |
88 | options_to_merge
89 | end
90 |
91 | def run_suite
92 | if @suite.tap(&:run).status == 0
93 | Phare.banner 'Everything looks good, keep on committing!'
94 | 0
95 | else
96 | Phare.banner 'Something’s wrong with your code style. Please fix it before committing.'
97 | 1
98 | end
99 | end
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/lib/phare/git.rb:
--------------------------------------------------------------------------------
1 | module Phare
2 | class Git
3 | def initialize(extensions, options)
4 | @extensions = extensions
5 | @options = options
6 | end
7 |
8 | def changed?
9 | @options[:diff] && changes.any?
10 | end
11 |
12 | def changes
13 | @changes ||= Phare.system_output('git status -s').split("\n").each_with_object([]) do |diff, memo|
14 | filename = diff.split(' ').last
15 |
16 | if diff =~ /^[A|M].*/ && @extensions.include?(File.extname(filename))
17 | memo << filename
18 | else
19 | next
20 | end
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/phare/version.rb:
--------------------------------------------------------------------------------
1 | module Phare
2 | VERSION = '1.0.1'
3 | end
4 |
--------------------------------------------------------------------------------
/phare.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'phare/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = 'phare'
8 | spec.version = Phare::VERSION
9 | spec.authors = ['Rémi Prévost']
10 | spec.email = ['rprevost@mirego.com']
11 | spec.description = 'Phare looks into your files and check for (Ruby, JavaScript and SCSS) coding style errors.'
12 | spec.summary = spec.description
13 | spec.homepage = 'https://github.com/mirego/phare'
14 | spec.license = 'BSD 3-Clause'
15 |
16 | spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19 | spec.require_paths = ['lib']
20 |
21 | spec.add_development_dependency 'bundler', '~> 1.3'
22 | spec.add_development_dependency 'rake'
23 | spec.add_development_dependency 'rspec'
24 | spec.add_development_dependency 'rubocop', '0.27'
25 | end
26 |
--------------------------------------------------------------------------------
/spec/phare/check/eslint_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Phare::Check::Eslint do
4 | let(:described_class) { Phare::Check::Eslint }
5 |
6 | describe :should_run? do
7 | let(:check) { described_class.new('.') }
8 | let(:config_exists?) { true }
9 | let(:path_exists?) { true }
10 | let(:local_which_output) { '' }
11 | let(:global_which_output) { '' }
12 | before do
13 | allow(Phare).to receive(:system_output).with('which eslint').and_return(global_which_output)
14 | allow(Phare).to receive(:system_output).with('which .node_modules/.bin/eslint').and_return(local_which_output)
15 | allow(File).to receive(:exist?).with(check.config).and_return(config_exists?)
16 | allow(Dir).to receive(:exist?).with(check.path).and_return(path_exists?)
17 | end
18 |
19 | context 'with found eslint command' do
20 | context 'for a local eslint' do
21 | let(:local_which_output) { 'node_modules/.bin/eslint' }
22 | it { expect(check).to be_able_to_run }
23 | end
24 |
25 | context 'for a global eslint' do
26 | let(:global_which_output) { 'eslint' }
27 | it { expect(check).to be_able_to_run }
28 | end
29 |
30 | context 'with only excluded files and the --diff option' do
31 | let(:global_which_output) { 'eslint' }
32 | let(:check) { described_class.new('.', diff: true) }
33 | let(:files) { ['foo.js'] }
34 |
35 | before do
36 | expect(check).to receive(:excluded_files).and_return(files).once
37 | expect(check.tree).to receive(:changes).and_return(files).at_least(:once)
38 | end
39 |
40 | it { expect(check.should_run?).to be_falsey }
41 | end
42 | end
43 |
44 | context 'with unfound eslint command' do
45 | let(:config_exists?) { false }
46 | let(:path_exists?) { false }
47 | it { expect(check).to_not be_able_to_run }
48 | end
49 | end
50 |
51 | describe :run do
52 | let(:check) { described_class.new('.') }
53 | let(:run!) { check.run }
54 |
55 | context 'with available Eslint' do
56 | let(:command) { check.command }
57 |
58 | before do
59 | expect(check).to receive(:should_run?).and_return(true)
60 | expect(Phare).to receive(:system).with(command)
61 | expect(Phare).to receive(:last_exit_status).and_return(eslint_exit_status)
62 | expect(check).to receive(:print_banner)
63 | end
64 |
65 | context 'without check errors' do
66 | let(:eslint_exit_status) { 0 }
67 | before { expect(Phare).to receive(:puts).with('Everything looks good from here!') }
68 | it { expect { run! }.to change { check.status }.to(eslint_exit_status) }
69 | end
70 |
71 | context 'with check errors' do
72 | let(:eslint_exit_status) { 1 }
73 | before { expect(Phare).to receive(:puts).with("Something went wrong. Program exited with #{eslint_exit_status}.") }
74 | it { expect { run! }.to change { check.status }.to(eslint_exit_status) }
75 | end
76 |
77 | context 'with --diff option' do
78 | let(:check) { described_class.new('.', diff: true) }
79 | let(:files) { ['foo.rb', 'bar.rb'] }
80 | let(:eslint_exit_status) { 0 }
81 |
82 | context 'without exclusions' do
83 | let(:command) { "eslint #{files.join(' ')}" }
84 |
85 | before do
86 | expect(check.tree).to receive(:changes).and_return(files).at_least(:once)
87 | expect(check).to receive(:excluded_files).and_return([]).once
88 | end
89 |
90 | it { expect { run! }.to change { check.status }.to(eslint_exit_status) }
91 | end
92 |
93 | context 'with exclusions' do
94 | let(:command) { 'eslint bar.rb' }
95 |
96 | before do
97 | expect(check).to receive(:excluded_files).and_return(['foo.rb']).once
98 | expect(check.tree).to receive(:changes).and_return(files).at_least(:once)
99 | end
100 |
101 | it { expect { run! }.to change { check.status }.to(eslint_exit_status) }
102 | end
103 | end
104 | end
105 |
106 | context 'with unavailable Eslint' do
107 | before do
108 | expect(check).to receive(:should_run?).and_return(false)
109 | expect(Phare).to_not receive(:system).with(check.command)
110 | expect(check).to_not receive(:print_banner)
111 | end
112 |
113 | it { expect { run! }.to change { check.status }.to(0) }
114 | end
115 | end
116 | end
117 |
--------------------------------------------------------------------------------
/spec/phare/check/rubocop_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Phare::Check::Rubocop do
4 | let(:described_class) { Phare::Check::Rubocop }
5 |
6 | describe :should_run? do
7 | let(:check) { described_class.new('.') }
8 | before { expect(Phare).to receive(:system_output).with('which rubocop').and_return(which_output) }
9 |
10 | context 'with found rubocop command' do
11 | let(:which_output) { 'rubocop' }
12 | it { expect(check).to be_able_to_run }
13 |
14 | context 'with only excluded files and the --diff option' do
15 | let(:check) { described_class.new('.', diff: true) }
16 | let(:files) { ['foo.rb'] }
17 |
18 | before do
19 | expect(check).to receive(:excluded_files).and_return(files).once
20 | expect(check.tree).to receive(:changes).and_return(files).at_least(:once)
21 | end
22 |
23 | it { expect(check.should_run?).to be_falsey }
24 | end
25 | end
26 |
27 | context 'with unfound rubocop command' do
28 | let(:which_output) { '' }
29 | it { expect(check).to_not be_able_to_run }
30 | end
31 | end
32 |
33 | describe :run do
34 | let(:check) { described_class.new('.') }
35 | let(:run!) { check.run }
36 |
37 | context 'with available Rubocop' do
38 | let(:command) { check.command }
39 |
40 | before do
41 | expect(check).to receive(:should_run?).and_return(true)
42 | expect(Phare).to receive(:system).with(command)
43 | expect(Phare).to receive(:last_exit_status).and_return(rubocop_exit_status)
44 | expect(check).to receive(:print_banner)
45 | end
46 |
47 | context 'with check errors' do
48 | let(:rubocop_exit_status) { 0 }
49 | it { expect { run! }.to change { check.status }.to(0) }
50 | end
51 |
52 | context 'without check errors' do
53 | let(:rubocop_exit_status) { 1337 }
54 | before { expect(Phare).to receive(:puts).with("Something went wrong. Program exited with #{rubocop_exit_status}.") }
55 | it { expect { run! }.to change { check.status }.to(rubocop_exit_status) }
56 | end
57 |
58 | context 'with --diff option' do
59 | let(:check) { described_class.new('.', diff: true) }
60 | let(:files) { ['foo.rb', 'bar.rb'] }
61 | let(:rubocop_exit_status) { 1337 }
62 |
63 | context 'without exclusions' do
64 | let(:command) { "rubocop #{files.join(' ')}" }
65 |
66 | before do
67 | expect(check.tree).to receive(:changes).and_return(files).at_least(:once)
68 | expect(check).to receive(:excluded_files).and_return([]).once
69 | end
70 |
71 | it { expect { run! }.to change { check.status }.to(rubocop_exit_status) }
72 | end
73 |
74 | context 'with exclusions' do
75 | let(:command) { 'rubocop bar.rb' }
76 |
77 | before do
78 | expect(check).to receive(:excluded_files).and_return(['foo.rb']).once
79 | expect(check.tree).to receive(:changes).and_return(files).at_least(:once)
80 | end
81 |
82 | it { expect { run! }.to change { check.status }.to(rubocop_exit_status) }
83 | end
84 | end
85 | end
86 |
87 | context 'with unavailable Rubocop' do
88 | before do
89 | expect(check).to receive(:should_run?).and_return(false)
90 | expect(Phare).to_not receive(:system).with(check.command)
91 | expect(check).to_not receive(:print_banner)
92 | end
93 |
94 | it { expect { run! }.to change { check.status }.to(0) }
95 | end
96 | end
97 | end
98 |
--------------------------------------------------------------------------------
/spec/phare/check/stylelint_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Phare::Check::Stylelint do
4 | let(:described_class) { Phare::Check::Stylelint }
5 |
6 | describe :should_run? do
7 | let(:check) { described_class.new('.') }
8 | let(:config_exists?) { true }
9 | let(:path_exists?) { true }
10 | let(:local_which_output) { '' }
11 | let(:global_which_output) { '' }
12 | before do
13 | allow(Phare).to receive(:system_output).with('which stylelint').and_return(global_which_output)
14 | allow(Phare).to receive(:system_output).with('which .node_modules/.bin/stylelint').and_return(local_which_output)
15 | allow(File).to receive(:exist?).with(check.config).and_return(config_exists?)
16 | allow(Dir).to receive(:exist?).with(check.path).and_return(path_exists?)
17 | end
18 |
19 | context 'with found stylelint command' do
20 | context 'for a local stylelint' do
21 | let(:local_which_output) { 'node_modules/.bin/stylelint' }
22 | it { expect(check).to be_able_to_run }
23 | end
24 |
25 | context 'for a global stylelint' do
26 | let(:global_which_output) { 'stylelint' }
27 | it { expect(check).to be_able_to_run }
28 | end
29 |
30 | context 'with only excluded files and the --diff option' do
31 | let(:global_which_output) { 'stylelint' }
32 | let(:check) { described_class.new('.', diff: true) }
33 | let(:files) { ['foo.scss'] }
34 |
35 | before do
36 | expect(check).to receive(:excluded_files).and_return(files).once
37 | expect(check.tree).to receive(:changes).and_return(files).at_least(:once)
38 | end
39 |
40 | it { expect(check.should_run?).to be_falsey }
41 | end
42 | end
43 |
44 | context 'with unfound stylelint command' do
45 | let(:config_exists?) { false }
46 | let(:path_exists?) { false }
47 | it { expect(check).to_not be_able_to_run }
48 | end
49 | end
50 |
51 | describe :run do
52 | let(:check) { described_class.new('.') }
53 | let(:run!) { check.run }
54 |
55 | context 'with available Stylelint' do
56 | let(:command) { check.command }
57 |
58 | before do
59 | expect(check).to receive(:should_run?).and_return(true)
60 | expect(Phare).to receive(:system).with(command)
61 | expect(Phare).to receive(:last_exit_status).and_return(stylelint_exit_status)
62 | expect(check).to receive(:print_banner)
63 | end
64 |
65 | context 'without check errors' do
66 | let(:stylelint_exit_status) { 0 }
67 | before { expect(Phare).to receive(:puts).with('Everything looks good from here!') }
68 | it { expect { run! }.to change { check.status }.to(stylelint_exit_status) }
69 | end
70 |
71 | context 'with check errors' do
72 | let(:stylelint_exit_status) { 1 }
73 | before { expect(Phare).to receive(:puts).with("Something went wrong. Program exited with #{stylelint_exit_status}.") }
74 | it { expect { run! }.to change { check.status }.to(stylelint_exit_status) }
75 | end
76 |
77 | context 'with --diff option' do
78 | let(:check) { described_class.new('.', diff: true) }
79 | let(:files) { ['foo.rb', 'bar.rb'] }
80 | let(:stylelint_exit_status) { 0 }
81 |
82 | context 'without exclusions' do
83 | let(:command) { "stylelint #{files.join(' ')}" }
84 |
85 | before do
86 | expect(check.tree).to receive(:changes).and_return(files).at_least(:once)
87 | expect(check).to receive(:excluded_files).and_return([]).once
88 | end
89 |
90 | it { expect { run! }.to change { check.status }.to(stylelint_exit_status) }
91 | end
92 |
93 | context 'with exclusions' do
94 | let(:command) { 'stylelint bar.rb' }
95 |
96 | before do
97 | expect(check).to receive(:excluded_files).and_return(['foo.rb']).once
98 | expect(check.tree).to receive(:changes).and_return(files).at_least(:once)
99 | end
100 |
101 | it { expect { run! }.to change { check.status }.to(stylelint_exit_status) }
102 | end
103 | end
104 | end
105 |
106 | context 'with unavailable Stylelint' do
107 | before do
108 | expect(check).to receive(:should_run?).and_return(false)
109 | expect(Phare).to_not receive(:system).with(check.command)
110 | expect(check).to_not receive(:print_banner)
111 | end
112 |
113 | it { expect { run! }.to change { check.status }.to(0) }
114 | end
115 | end
116 | end
117 |
--------------------------------------------------------------------------------
/spec/phare/check_suite_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Phare::CheckSuite do
4 | let(:described_class) { Phare::CheckSuite }
5 |
6 | describe :run do
7 | let(:options) { { directory: '.' } }
8 | let(:suite) { described_class.new(options) }
9 |
10 | before do
11 | Phare::CheckSuite::DEFAULT_CHECKS.values.each_with_index do |check, index|
12 | exit_status = exit_status_proc.call(index)
13 | allow_any_instance_of(check).to receive(:run)
14 | allow_any_instance_of(check).to receive(:status).and_return(exit_status)
15 | end
16 | end
17 |
18 | context 'with a single failing check' do
19 | let(:exit_status_proc) do
20 | proc { |index| index == 2 ? 1337 : 0 }
21 | end
22 |
23 | it { expect { suite.run }.to change { suite.status }.to(1337) }
24 | end
25 |
26 | context 'with a multiple failing checks' do
27 | let(:exit_status_proc) do
28 | proc { |index| (index * 500) + 20 }
29 | end
30 |
31 | it { expect { suite.run }.to change { suite.status }.to(20) }
32 | end
33 |
34 | context 'with no failing check' do
35 | let(:exit_status_proc) { proc { 0 } }
36 |
37 | it { expect { suite.run }.to change { suite.status }.to(0) }
38 | end
39 | end
40 |
41 | describe :checks do
42 | let(:options) { { directory: '.', skip: skip, only: only } }
43 | let(:suite) { described_class.new(options) }
44 | let(:skip) { [] }
45 | let(:only) { [] }
46 |
47 | context 'with "only" option' do
48 | let(:only) { [:rubocop, :foo, :eslint] }
49 | it { expect(suite.checks).to eql [:rubocop, :eslint] }
50 | end
51 |
52 | context 'with "skip" option' do
53 | let(:skip) { [:stylelint, :foo] }
54 | it { expect(suite.checks).to eql [:rubocop, :eslint] }
55 | end
56 |
57 | context 'with both "only" and "skip" option' do
58 | let(:skip) { [:stylelint, :rubocop] }
59 | let(:only) { [:stylelint, :foo, :eslint] }
60 | it { expect(suite.checks).to eql [:stylelint, :eslint] }
61 | end
62 |
63 | context 'with both "only" and "skip" option' do
64 | it { expect(suite.checks).to eql Phare::CheckSuite::DEFAULT_CHECKS.keys }
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/spec/phare/cli_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Phare::CLI do
4 | let(:described_class) { Phare::CLI }
5 |
6 | let(:cli) { described_class.new(env, argv) }
7 | let(:argv) { [] }
8 | let(:run!) { cli.run }
9 | let(:env) { {} }
10 |
11 | describe :run do
12 | context 'with version flag' do
13 | let(:cli) { described_class.new(env, ['--version']) }
14 |
15 | before do
16 | expect(Phare).to receive(:puts).with Phare::VERSION
17 | end
18 |
19 | it { expect { run! }.to exit_with_code(0) }
20 | end
21 |
22 | context 'with legacy code check skipping' do
23 | let(:env) { { 'SKIP_CODE_CHECK' => '1' } }
24 | it { expect { run! }.to exit_with_code(0) }
25 | end
26 |
27 | context 'with code check skipping' do
28 | let(:env) { { 'SKIP_PHARE' => '1' } }
29 | it { expect { run! }.to exit_with_code(0) }
30 | end
31 |
32 | context 'without code check skipping' do
33 | let(:env) { {} }
34 |
35 | context 'with suite errors' do
36 | before do
37 | expect(cli.suite).to receive(:run)
38 | expect(cli.suite).to receive(:status).and_return(1337)
39 | end
40 |
41 | it { expect { run! }.to exit_with_code(1) }
42 | end
43 |
44 | context 'without suite errors' do
45 | before do
46 | expect(cli.suite).to receive(:run)
47 | expect(cli.suite).to receive(:status).and_return(0)
48 | end
49 |
50 | it { expect { run! }.to exit_with_code(0) }
51 | end
52 | end
53 | end
54 |
55 | describe :parsed_options do
56 | let(:parsed_options) { cli.send(:parsed_options, argv) }
57 |
58 | before do
59 | expect(cli).to receive(:parsed_options_from_yaml).and_return(skip: ['scsslint'])
60 | expect(cli).to receive(:parsed_options_from_arguments).and_return(skip: ['rubocop'])
61 | end
62 |
63 | it { expect(parsed_options[:directory]).to eql Dir.pwd }
64 | it { expect(parsed_options[:skip]).to eql [:rubocop] }
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/spec/phare/git_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Phare::Git do
4 | let(:described_class) { Phare::Git }
5 |
6 | let(:extensions) { ['.rb'] }
7 | let(:options) { { diff: true } }
8 | let(:git) { described_class.new(extensions, options) }
9 |
10 | describe :changed? do
11 | context 'with --diff options' do
12 | before { expect(git).to receive(:changes).and_return(changes) }
13 |
14 | context 'with changes' do
15 | let(:changes) { %w(foo.rb) }
16 |
17 | it { expect(git.changed?).to eq(true) }
18 | end
19 |
20 | context 'without changes' do
21 | let(:changes) { [] }
22 |
23 | it { expect(git.changed?).to eq(false) }
24 | end
25 | end
26 |
27 | context 'without --diff options' do
28 | let(:options) { { diff: false } }
29 |
30 | it { expect(git.changed?).to eq(false) }
31 | end
32 | end
33 |
34 | describe :changes do
35 | before { expect(Phare).to receive(:system_output).with('git status -s').and_return(tree) }
36 |
37 | context 'with empty tree' do
38 | let(:tree) { '' }
39 |
40 | it { expect(git.changes).to be_empty }
41 | end
42 |
43 | context 'with untracked file' do
44 | let(:tree) { '?? foo.rb' }
45 |
46 | it { expect(git.changes).to be_empty }
47 | end
48 |
49 | context 'with added file' do
50 | let(:tree) { "A foo.rb\nAM bar.rb" }
51 |
52 | it { expect(git.changes).to match_array(%w(foo.rb bar.rb)) }
53 | end
54 |
55 | context 'with modified file' do
56 | let(:tree) { "M foo.rb\nDM bar.rb\n M foobar.rb" }
57 |
58 | it { expect(git.changes).to match_array(%w(foo.rb)) }
59 | end
60 |
61 | context 'with deleted file' do
62 | let(:tree) { " D foo.rb\nMD bar.rb\n D foobar.rb" }
63 |
64 | it { expect(git.changes).to match_array(%w(bar.rb)) }
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2 |
3 | require 'rspec'
4 | require 'phare'
5 |
6 | RSpec.configure do |config|
7 | config.before(:each) do
8 | # Disable all logging
9 | allow(Phare).to receive(:puts)
10 | end
11 | end
12 |
13 | RSpec::Matchers.define :be_able_to_run do
14 | match do |actual|
15 | actual.send(:should_run?) == true
16 | end
17 | end
18 |
19 | RSpec::Matchers.define :exit_with_code do |expected_code|
20 | actual = nil
21 |
22 | match do |block|
23 | begin
24 | block.call
25 | rescue SystemExit => e
26 | actual = e.status
27 | end
28 |
29 | actual && actual == expected_code
30 | end
31 |
32 | supports_block_expectations do
33 | true
34 | end
35 |
36 | failure_message do
37 | "expected block to call exit(#{expected_code}) but exit" +
38 | (actual.nil? ? ' not called' : "(#{actual}) was called")
39 | end
40 |
41 | failure_message_when_negated do
42 | "expected block not to call exit(#{expected_code})"
43 | end
44 |
45 | description do
46 | "expect block to call exit(#{expected_code})"
47 | end
48 | end
49 |
--------------------------------------------------------------------------------