Skip to content

Commit 9dc6299

Browse files
author
Landon Wilkins
committed
lint: encourage specs, fixes SD-1024
test plan: - change a file w/ a path that matches: /(app|lib|public)\/.*\.(coffee|js|jsx|html|erb|rb)$/ but not: /(bower|mediaelement|shims|vendor)\// - ./script/tatl_tael - verify you see: [error] => Your commit does not include specs. Please add some to verify your changes. Change-Id: I725daf72c14180bb29c440f9867244a43dc5169d Reviewed-on: https://gerrit.instructure.com/77579 Tested-by: Jenkins Reviewed-by: Jon Jensen <jon@instructure.com> Product-Review: Jon Jensen <jon@instructure.com> QA-Review: Jon Jensen <jon@instructure.com>
1 parent 87c52d7 commit 9dc6299

11 files changed

Lines changed: 306 additions & 0 deletions

File tree

gems/tatl_tael/Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source 'https://rubygems.org'
2+
3+
# Specify your gem's dependencies in tatl_tael.gemspec
4+
gemspec

gems/tatl_tael/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# TatlTael
2+
3+
TatlTael provides linting on the commit as a whole.
4+
5+
## Usage
6+
7+
```
8+
require "tatl_tael"
9+
10+
linter = TatlTael::Linter.new(git_dir: git_dir)
11+
12+
linter.ensure_specs do
13+
puts "this will be printed if there are ruby additions or modifications,"\
14+
" but no spec additions or modifications."
15+
end
16+
```
17+
18+

gems/tatl_tael/Rakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require "bundler/gem_tasks"

gems/tatl_tael/lib/tatl_tael.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require_relative 'tatl_tael/linter'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module TatlTael
2+
class Change
3+
attr_reader :path
4+
5+
attr_reader :status
6+
private :status
7+
8+
def initialize(status, path)
9+
@status = status
10+
@path = path
11+
end
12+
13+
def added?
14+
status == "A"
15+
end
16+
17+
def deleted?
18+
status == "D"
19+
end
20+
21+
def modified?
22+
status == "M"
23+
end
24+
end
25+
end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
require_relative 'change'
2+
3+
module TatlTael
4+
class GitProxy
5+
attr_reader :git_dir
6+
private :git_dir
7+
8+
def initialize(git_dir = nil)
9+
@git_dir = git_dir
10+
end
11+
12+
def changes(git_dir)
13+
command = "git diff-tree --no-commit-id --name-status -r HEAD"
14+
raw_changes = shell(command, git_dir)
15+
raw_changes.split("\n").map do |raw_change|
16+
status, path = raw_change.split("\t")
17+
TatlTael::Change.new(status, path)
18+
end
19+
end
20+
21+
private
22+
23+
def shell(command, git_dir)
24+
if git_dir
25+
Dir.chdir(git_dir) do
26+
Kernel.send(:`, command)
27+
end
28+
else
29+
Kernel.send(:`, command)
30+
end
31+
end
32+
end
33+
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
require_relative 'git_proxy'
2+
3+
module TatlTael
4+
class Linter
5+
attr_reader :git
6+
private :git
7+
8+
attr_reader :git_dir
9+
private :git_dir
10+
11+
def initialize(git_dir:, git: nil)
12+
@git_dir = git_dir
13+
@git = git || TatlTael::GitProxy.new(git_dir)
14+
end
15+
16+
def ensure_specs
17+
yield if needs_specs? && !spec_changes?
18+
end
19+
20+
private
21+
22+
NEED_SPECS_REGEX = /(app|lib|public)\/.*\.(coffee|js|jsx|html|erb|rb)$/
23+
EXCLUDED_SUB_DIR_REGEX = /(bower|mediaelement|shims|vendor)\//
24+
def needs_specs?
25+
changes.any? do |change|
26+
change.path =~ NEED_SPECS_REGEX &&
27+
change.path !~ EXCLUDED_SUB_DIR_REGEX &&
28+
!change.deleted?
29+
end
30+
end
31+
32+
SPEC_REGEX = /\/(spec|spec_canvas|test)\//
33+
def spec_changes?
34+
changes.any? do |change|
35+
change.path =~ SPEC_REGEX &&
36+
!change.deleted?
37+
end
38+
end
39+
40+
def changes
41+
@changes ||= git.changes(git_dir)
42+
end
43+
end
44+
end

gems/tatl_tael/spec/linter_spec.rb

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
require 'spec_helper'
2+
3+
describe TatlTael::Linter do
4+
shared_examples "yields" do |raw_changes|
5+
let(:changes) { raw_changes.map { |c| double(c) } }
6+
7+
it "yields" do
8+
expect { |b| subject.ensure_specs(&b) }.to yield_with_no_args
9+
end
10+
end
11+
12+
shared_examples "does not yield" do |raw_changes|
13+
let(:changes) { raw_changes.map { |c| double(c) } }
14+
15+
it "does not yield" do
16+
expect { |b| subject.ensure_specs(&b) }.not_to yield_with_no_args
17+
end
18+
end
19+
20+
shared_examples "change combos" do |change_path|
21+
context "not deletion" do
22+
context "no spec changes" do
23+
include_examples "yields",
24+
[{ path: change_path, deleted?: false }]
25+
end
26+
context "has spec non deletions" do
27+
include_examples "does not yield",
28+
[{ path: change_path, deleted?: false },
29+
{ path: SPEC_CHANGE, deleted?: false }]
30+
include_examples "does not yield",
31+
[{ path: change_path, deleted?: false },
32+
{ path: SPEC_CANVAS_CHANGE, deleted?: false }]
33+
include_examples "does not yield",
34+
[{ path: change_path, deleted?: false },
35+
{ path: TEST_CHANGE, deleted?: false }]
36+
end
37+
context "has spec deletions" do
38+
include_examples "yields",
39+
[{ path: change_path, deleted?: false },
40+
{ path: SPEC_CHANGE, deleted?: true }]
41+
include_examples "yields",
42+
[{ path: change_path, deleted?: false },
43+
{ path: SPEC_CANVAS_CHANGE, deleted?: true }]
44+
include_examples "yields",
45+
[{ path: change_path, deleted?: false },
46+
{ path: TEST_CHANGE, deleted?: true }]
47+
end
48+
end
49+
context "deletion" do
50+
include_examples "does not yield",
51+
[{ path: change_path, deleted?: true }]
52+
end
53+
end
54+
55+
let(:subject) { TatlTael::Linter.new(git_dir: ".") }
56+
57+
SPEC_CHANGE = "spec/controllers/accounts_controller_spec.rb"
58+
SPEC_CANVAS_CHANGE = "spec_canvas/selenium/analytics_course_view_spec.rb"
59+
TEST_CHANGE = "gems/acts_as_list/test/list_test.rb"
60+
61+
APP_RB_PATH = "app/controllers/accounts_controller.rb"
62+
APP_ERB_PATH = "app/views/announcements/index.html.erb"
63+
APP_JSX_PATH = "app/jsx/editor/SwitchEditorControl.jsx"
64+
APP_COFFEE_PATH = "app/coffeescripts/calendar/CalendarEvent.coffee"
65+
LIB_RB_PATH = "lib/api_routes.rb"
66+
PUBLIC_HTML_PATH = "public/partials/_license_help.html"
67+
PUBLIC_JS_PATH = "public/javascripts/account_settings.js"
68+
PUBLIC_BOWER_JS_PATH = "public/javascripts/bower/axios/dist/axios.amd.js"
69+
PUBLIC_ME_JS_PATH = "public/javascripts/mediaelement/mep-feature-speed-instructure.js"
70+
PUBLIC_VENDOR_JS_PATH = "public/javascripts/vendor/bootstrap/bootstrap-dropdown.js"
71+
72+
before(:each) do
73+
allow(subject).to receive(:changes).and_return(changes)
74+
end
75+
76+
describe "#ensure_specs" do
77+
context "in app" do
78+
context "has ruby changes" do
79+
include_examples "change combos", APP_RB_PATH
80+
end
81+
context "has erb changes" do
82+
include_examples "change combos", APP_ERB_PATH
83+
end
84+
context "has jsx changes" do
85+
include_examples "change combos", APP_JSX_PATH
86+
end
87+
context "has coffee changes" do
88+
include_examples "change combos", APP_COFFEE_PATH
89+
end
90+
end
91+
92+
context "in lib" do
93+
context "has ruby changes" do
94+
include_examples "change combos", LIB_RB_PATH
95+
end
96+
end
97+
98+
context "in public" do
99+
context "has html changes" do
100+
include_examples "change combos", PUBLIC_HTML_PATH
101+
end
102+
context "has js changes" do
103+
include_examples "change combos", PUBLIC_JS_PATH
104+
end
105+
end
106+
107+
context "in excluded public sub dirs" do
108+
context "bower" do
109+
include_examples "does not yield",
110+
[{ path: PUBLIC_BOWER_JS_PATH, deleted?: false }]
111+
end
112+
context "mediaelement" do
113+
include_examples "does not yield",
114+
[{ path: PUBLIC_ME_JS_PATH, deleted?: false }]
115+
end
116+
context "vendor" do
117+
include_examples "does not yield",
118+
[{ path: PUBLIC_VENDOR_JS_PATH, deleted?: false }]
119+
end
120+
end
121+
end
122+
end

gems/tatl_tael/spec/spec_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2+
require 'tatl_tael'

gems/tatl_tael/tatl_tael.gemspec

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# coding: utf-8
2+
lib = File.expand_path('../lib', __FILE__)
3+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4+
5+
Gem::Specification.new do |spec|
6+
spec.name = "tatl_tael"
7+
spec.version = "0.0.1"
8+
spec.authors = ["Landon Wilkins"]
9+
spec.email = ["lwilkins@instructure.com"]
10+
spec.summary = "Commit level linting."
11+
12+
spec.files = Dir.glob("{lib,spec,bin}/**/*")
13+
spec.test_files = spec.files.grep(%r{^spec/})
14+
spec.require_paths = ["lib"]
15+
16+
spec.add_development_dependency "rspec", "~> 3.0"
17+
end
18+
19+

0 commit comments

Comments
 (0)