Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add support for Haml 5
Haml 5 is has been in Beta 2 in their repository for a while and has
some breaking API changed when compared with Haml 4. By adding an
adapter layer, we can maintain compatibility with both Haml 4 and 5.

To test on both Haml 4 and Haml 5, this introduces the Appraisal gem
that allows you specify different Gemfiles to test against. There was
a slight incompatibility with the Overcommit tool that we use during our
build process that required a tweak to the Overcommit configuration.

Overcommit's bundler integration works by overriding the
`BUNDLER_GEMFILE` environment variable. Appraisal works by specifying
this environment variable before running a command. The two were
conflicting and Overcommit was always overriding the Gemfile.

By disabling this feature, we can test on both Haml 4 and Haml 5 with an
easy-to-use command, yet still use the Overcommit gem as intended, since
it is present in both of the generated Gemfiles.

Closes #152
  • Loading branch information
michaelherold committed Feb 18, 2017
1 parent 5b1cb13 commit 2403072
Show file tree
Hide file tree
Showing 16 changed files with 208 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,3 +1,4 @@
Gemfile.lock
/coverage
/gemfiles/*.lock
/pkg
3 changes: 0 additions & 3 deletions .overcommit.yml
@@ -1,6 +1,3 @@
# Run Overcommit within a Bundler context using this repo's Gemfile
gemfile: Gemfile

PreCommit:
BundleCheck:
enabled: true
Expand Down
4 changes: 4 additions & 0 deletions .travis.yml
Expand Up @@ -10,6 +10,10 @@ rvm:
- 2.2
- 2.3.1

gemfile:
- gemfiles/haml4.gemfile
- gemfiles/haml5.gemfile

before_script:
- git config --local user.email "travis@travis.ci"
- git config --local user.name "Travis CI"
Expand Down
7 changes: 7 additions & 0 deletions Appraisals
@@ -0,0 +1,7 @@
appraise 'haml4' do
gem 'haml', '~> 4'
end

appraise 'haml5' do
gem 'haml', '~> 5.0.0.beta.2', git: 'https://github.com/haml/haml.git'
end
4 changes: 4 additions & 0 deletions Gemfile
Expand Up @@ -2,9 +2,13 @@ source 'https://rubygems.org'

gemspec

gem 'haml', '~> 5.0.0.beta.2', git: 'https://github.com/haml/haml'
gem 'rspec', '~> 3.0'
gem 'rspec-its', '~> 1.0'

# For testing against multiple versions of Haml
gem 'appraisal'

# Run all pre-commit hooks via Overcommit during CI runs
gem 'overcommit', '0.37.0'

Expand Down
10 changes: 7 additions & 3 deletions README.md
Expand Up @@ -221,13 +221,17 @@ attribute.
We love getting feedback with or without pull requests. If you do add a new
feature, please add tests so that we can avoid breaking it in the future.

Speaking of tests, we use `rspec`, which can be run by executing the following
from the root directory of the repository:
Speaking of tests, we use [Appraisal] to test against both HAML 4 and the
upcoming HAML 5 and use `rspec` to write our tests. To run the test suite,
execute the following from the root directory of the repository:

```bash
bundle exec rspec
appraisal bundle install
appraisal bundle exec rspec
```

[Appraisal]: https://github.com/thoughtbot/appraisal

## Community

All major discussion surrounding HAML-Lint happens on the
Expand Down
13 changes: 13 additions & 0 deletions gemfiles/haml4.gemfile
@@ -0,0 +1,13 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "haml", "~> 4"
gem "rspec", "~> 3.0"
gem "rspec-its", "~> 1.0"
gem "appraisal"
gem "overcommit", "0.37.0"
gem "rubocop", "0.47.0"
gem "coveralls", :require => false

gemspec :path => "../"
13 changes: 13 additions & 0 deletions gemfiles/haml5.gemfile
@@ -0,0 +1,13 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "haml", "~> 5.0.0.beta.2", :git => "https://github.com/haml/haml.git"
gem "rspec", "~> 3.0"
gem "rspec-its", "~> 1.0"
gem "appraisal"
gem "overcommit", "0.37.0"
gem "rubocop", "0.47.0"
gem "coveralls", :require => false

gemspec :path => "../"
2 changes: 1 addition & 1 deletion haml_lint.gemspec
Expand Up @@ -21,7 +21,7 @@ Gem::Specification.new do |s|

s.required_ruby_version = '>= 1.9.3'

s.add_dependency 'haml', '~> 4.0'
s.add_dependency 'haml', '>= 4.0', '< 5.1'
s.add_dependency 'rake', '>= 10', '< 13'
s.add_dependency 'rubocop', '>= 0.47.0'
s.add_dependency 'sysexits', '~> 1.1'
Expand Down
36 changes: 36 additions & 0 deletions lib/haml_lint/adapter.rb
@@ -0,0 +1,36 @@
require 'haml_lint/adapter/haml_4'
require 'haml_lint/adapter/haml_5'
require 'haml_lint/exceptions'

module HamlLint
# Determines the adapter to use for the current Haml version
class Adapter
# Detects the adapter to use for the current Haml version
#
# @example
# HamlLint::Adapter.detect_class.new('%div')
#
# @api public
# @return [Class] the adapter class
# @raise [HamlLint::Exceptions::UnknownHamlVersion]
def self.detect_class
version = haml_version
case version
when '~> 4.0' then HamlLint::Adapter::Haml4
when '~> 5.0' then HamlLint::Adapter::Haml5
else fail HamlLint::Exceptions::UnknownHamlVersion, "Cannot handle Haml version: #{version}"
end
end

# Determines the approximate version of Haml that is installed
#
# @api private
# @return [String] the approximate Haml version
def self.haml_version
Gem::Version
.new(Haml::VERSION)
.approximate_recommendation
end
private_class_method :haml_version
end
end
40 changes: 40 additions & 0 deletions lib/haml_lint/adapter/haml_4.rb
@@ -0,0 +1,40 @@
module HamlLint
class Adapter
# Adapts the Haml::Parser from Haml 4 for use in HamlLint
# :reek:UncommunicativeModuleName
class Haml4 < Adapter
extend Forwardable

# Parses the specified Haml code into an abstract syntax tree
#
# @example
# HamlLint::Adapter::Haml4.new('%div')
#
# @api public
# @param source [String] Haml code to parse
# @param options [Haml::Options]
def initialize(source, options = Haml::Options.new)
@parser = Haml::Parser.new(source, options)
end

# @!method
# Parses the source code into an abstract syntax tree
#
# @example
# HamlLint::Adapter::Haml4.new('%div')
#
# @api public
# @return [Haml::Parser::ParseNode]
# @raise [Haml::Error]
def_delegator :parser, :parse

private

# The Haml parser to adapt for HamlLint
#
# @api private
# @return [Haml::Parser] the Haml 4 parser
attr_reader :parser
end
end
end
46 changes: 46 additions & 0 deletions lib/haml_lint/adapter/haml_5.rb
@@ -0,0 +1,46 @@
module HamlLint
class Adapter
# Adapts the Haml::Parser from Haml 5 for use in HamlLint
# :reek:UncommunicativeModuleName
class Haml5 < Adapter
# Parses the specified Haml code into an abstract syntax tree
#
# @example
# HamlLint::Adapter::Haml5.new('%div')
#
# @api public
# @param source [String] Haml code to parse
# @param options [Haml::Options]
def initialize(source, options = Haml::Options.new)
@source = source
@parser = Haml::Parser.new(options)
end

# Parses the source code into an abstract syntax tree
#
# @example
# HamlLint::Adapter::Haml5.new('%div').parse
#
# @api public
# @return [Haml::Parser::ParseNode]
# @raise [Haml::Error]
def parse
parser.call(source)
end

private

# The Haml parser to adapt for HamlLint
#
# @api private
# @return [Haml::Parser] the Haml 4 parser
attr_reader :parser

# The Haml code to parse
#
# @api private
# @return [String] Haml code to parse
attr_reader :source
end
end
end
4 changes: 3 additions & 1 deletion lib/haml_lint/document.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'haml_lint/adapter'

module HamlLint
# Represents a parsed Haml document and its associated metadata.
class Document
Expand Down Expand Up @@ -43,7 +45,7 @@ def process_source(source)
@source = strip_frontmatter(source)
@source_lines = @source.split("\n")

@tree = process_tree(Haml::Parser.new(@source, Haml::Options.new).parse)
@tree = process_tree(HamlLint::Adapter.detect_class.new(@source).parse)
rescue Haml::Error => ex
error = HamlLint::Exceptions::ParseError.new(ex.message, ex.line)
raise error
Expand Down
3 changes: 3 additions & 0 deletions lib/haml_lint/exceptions.rb
Expand Up @@ -15,4 +15,7 @@ class ParseError < ::Haml::SyntaxError; end
# Raised when attempting to execute `Runner` with options that would result in
# no linters being enabled.
class NoLintersError < StandardError; end

# Raised when an unsupported Haml version is detected
class UnknownHamlVersion < StandardError; end
end
4 changes: 3 additions & 1 deletion lib/haml_lint/tree/tag_node.rb
Expand Up @@ -184,9 +184,11 @@ def remove_inner_whitespace?
# Whether this node had a `>` after it signifying that outer whitespace
# should be removed.
#
# rubocop:disable Style/DoubleNegation
#
# @return [true,false]
def remove_outer_whitespace?
@value[:nuke_outer_whitespace]
!!@value[:nuke_outer_whitespace]
end

# Returns the script source that will be evaluated to produce this tag's
Expand Down
27 changes: 27 additions & 0 deletions spec/haml_lint/adapter_spec.rb
@@ -0,0 +1,27 @@
require 'spec_helper'

RSpec.describe HamlLint::Adapter do
describe '.detect_class' do
subject { described_class.detect_class }

context 'on Haml 4' do
before { stub_const('Haml::VERSION', '4.0.7') }

it { should == HamlLint::Adapter::Haml4 }
end

context 'on Haml 5' do
before { stub_const('Haml::VERSION', '5.0.0.beta.2') }

it { should == HamlLint::Adapter::Haml5 }
end

context 'on unknown version of Haml' do
before { stub_const('Haml::VERSION', '3.0.0') }

it 'raises an error' do
expect { subject }.to raise_error(HamlLint::Exceptions::UnknownHamlVersion)
end
end
end
end

0 comments on commit 2403072

Please sign in to comment.