lintrunner 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.scss-lint.yml ADDED
@@ -0,0 +1,191 @@
1
+ # Default application configuration that all configurations inherit from.
2
+
3
+ scss_files: "**/*.scss"
4
+
5
+ linters:
6
+ BangFormat:
7
+ enabled: true
8
+ space_before_bang: true
9
+ space_after_bang: false
10
+
11
+ BorderZero:
12
+ enabled: true
13
+ convention: none
14
+
15
+ ColorKeyword:
16
+ enabled: true
17
+
18
+ ColorVariable:
19
+ enabled: true
20
+
21
+ Comment:
22
+ enabled: true
23
+
24
+ DebugStatement:
25
+ enabled: true
26
+
27
+ DeclarationOrder:
28
+ enabled: false
29
+
30
+ DuplicateProperty:
31
+ enabled: false
32
+
33
+ ElsePlacement:
34
+ enabled: true
35
+ style: same_line
36
+
37
+ EmptyLineBetweenBlocks:
38
+ enabled: true
39
+ ignore_single_line_blocks: false
40
+
41
+ EmptyRule:
42
+ enabled: true
43
+
44
+ FinalNewline:
45
+ enabled: true
46
+ present: true
47
+
48
+ HexLength:
49
+ enabled: true
50
+ style: long
51
+
52
+ HexNotation:
53
+ enabled: true
54
+ style: lowercase
55
+
56
+ HexValidation:
57
+ enabled: true
58
+
59
+ IdSelector:
60
+ enabled: false
61
+
62
+ ImportantRule:
63
+ enabled: true
64
+
65
+ ImportPath:
66
+ enabled: true
67
+ leading_underscore: false
68
+ filename_extension: false
69
+
70
+ Indentation:
71
+ enabled: true
72
+ allow_non_nested_indentation: false
73
+ character: space
74
+ width: 2
75
+
76
+ LeadingZero:
77
+ enabled: false
78
+ style: exclude_zero
79
+
80
+ MergeableSelector:
81
+ enabled: true
82
+ force_nesting: false
83
+
84
+ NameFormat:
85
+ enabled: true
86
+ allow_leading_underscore: true
87
+ convention: hyphenated_lowercase
88
+
89
+ NestingDepth:
90
+ enabled: true
91
+ max_depth: 4
92
+
93
+ PlaceholderInExtend:
94
+ enabled: true
95
+
96
+ PropertyCount:
97
+ enabled: false
98
+ include_nested: false
99
+ max_properties: 10
100
+
101
+ PropertySortOrder:
102
+ enabled: true
103
+ ignore_unspecified: false
104
+ separate_groups: false
105
+
106
+ PropertySpelling:
107
+ enabled: true
108
+ extra_properties: []
109
+
110
+ QualifyingElement:
111
+ enabled: false
112
+ allow_element_with_attribute: false
113
+ allow_element_with_class: false
114
+ allow_element_with_id: false
115
+
116
+ SelectorDepth:
117
+ enabled: true
118
+ max_depth: 3
119
+
120
+ SelectorFormat:
121
+ enabled: true
122
+ convention: hyphenated_BEM
123
+
124
+ Shorthand:
125
+ enabled: true
126
+
127
+ SingleLinePerProperty:
128
+ enabled: true
129
+ allow_single_line_rule_sets: false
130
+
131
+ SingleLinePerSelector:
132
+ enabled: true
133
+
134
+ SpaceAfterComma:
135
+ enabled: true
136
+
137
+ SpaceAfterPropertyColon:
138
+ enabled: true
139
+ style: one_space
140
+
141
+ SpaceAfterPropertyName:
142
+ enabled: true
143
+
144
+ SpaceBeforeBrace:
145
+ enabled: true
146
+ style: space # or 'new_line'
147
+ allow_single_line_padding: false
148
+
149
+ SpaceBetweenParens:
150
+ enabled: true
151
+ spaces: 0
152
+
153
+ StringQuotes:
154
+ enabled: false
155
+ style: single_quotes
156
+
157
+ TrailingSemicolon:
158
+ enabled: true
159
+
160
+ TrailingZero:
161
+ enabled: true
162
+
163
+ UnnecessaryMantissa:
164
+ enabled: true
165
+
166
+ UnnecessaryParentReference:
167
+ enabled: true
168
+
169
+ UrlFormat:
170
+ enabled: true
171
+
172
+ UrlQuotes:
173
+ enabled: true
174
+
175
+ VariableForProperty:
176
+ enabled: true
177
+ properties:
178
+ - color
179
+ - font
180
+
181
+ VendorPrefixes:
182
+ enabled: false
183
+ identifier_list: base
184
+ include: []
185
+ exclude: []
186
+
187
+ ZeroUnit:
188
+ enabled: true
189
+
190
+ Compass::*:
191
+ enabled: false
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in lintrunner.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Ivan Tse
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Lintrunner
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'lintrunner'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install lintrunner
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/[my-github-username]/lintrunner/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/lintrunner ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), "../lib"))
4
+
5
+ require "lintrunner"
6
+ require "lintrunner/cli"
7
+
8
+ Lintrunner::CLI.new.run(ARGV)
@@ -0,0 +1,97 @@
1
+ require "lintrunner/options"
2
+
3
+ module Lintrunner
4
+ class CLI
5
+
6
+ # Takes an array of arguments
7
+ # Returns exit code
8
+ def run(args)
9
+ options = Options.new.parse(args)
10
+
11
+ handle_options(options)
12
+
13
+ path = options[:path]
14
+ context = options[:context]
15
+
16
+ # If context is different from path that we want to lint, check that it's up to date
17
+ path != context && is_up_to_date?(context)
18
+
19
+ warnings = []
20
+ reporter = initialize_reporter(options[:reporter], path)
21
+
22
+ config = JSON.parse(File.read(Dir.pwd + "/.lintrunner_config"))
23
+ include_paths(config["include_paths"])
24
+ require_files(config["require"])
25
+
26
+ config["linters"].each do |name, config|
27
+ next if config["disabled"]
28
+ if config["command"] && config["parser"]
29
+ parser = Lintrunner::Parser.const_get(config["parser"]).new
30
+ executor = Lintrunner::Executor.new(
31
+ command: config["command"],
32
+ parser: parser
33
+ )
34
+ else
35
+ executor = Lintrunner::Executor.new(
36
+ plugin: Lintrunner::Plugin.const_get(config["plugin"])
37
+ )
38
+ end
39
+
40
+ runner = Lintrunner::Runner.const_get(config["runner"]).new(
41
+ path: path,
42
+ match: config["match"],
43
+ executor: executor
44
+ )
45
+ reporter.start(name)
46
+ results = runner.run(reporter)
47
+ reporter.finish(results)
48
+ warnings.concat(results)
49
+ end
50
+ exit warnings.empty? ? 0 : 1
51
+ end
52
+
53
+ private
54
+
55
+ def initialize_reporter(reporter, path)
56
+ Lintrunner::Reporter.const_get(reporter.capitalize).new(path)
57
+ end
58
+
59
+ def handle_options(options)
60
+
61
+ end
62
+
63
+ def is_up_to_date?(path)
64
+ context_repo = Rugged::Repository.new(path)
65
+ return unless context_repo.branches['master'].head?
66
+ return if context_repo.branches['master'].upstream.nil?
67
+ Dir.chdir path do
68
+ # update remote ref
69
+ `git fetch #{context_repo.branches['master'].upstream.name.split("/").join(" ")} > /dev/null 2>&1`
70
+ status_output = `git status`
71
+ if status_output =~ /Your branch is behind .* and can be fast-forwarded./
72
+
73
+ puts "Repo is not up to date!".color(:red)
74
+ puts "Would you like to update it (Y/N)?"
75
+ input = STDIN.gets
76
+ if input.strip == "Y"
77
+ `git pull --ff-only #{context_repo.branches['master'].upstream.name.split("/").join(" ")}`
78
+ else
79
+ exit 1
80
+ end
81
+ end
82
+ end
83
+ rescue Rugged::RepositoryError => e
84
+ # noop
85
+ end
86
+
87
+ def include_paths(include_paths = [])
88
+ $:.unshift(*include_paths)
89
+ end
90
+
91
+ def require_files(files)
92
+ files.each do |file|
93
+ require file
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,23 @@
1
+ # Encapsulates how to execute a lint either through a command line string with a parser or with a plugin
2
+ module Lintrunner
3
+ class Executor
4
+
5
+ attr_reader :command, :parser, :plugin
6
+
7
+ def initialize(options = {})
8
+ @command = options[:command]
9
+ @parser = options[:parser]
10
+ @plugin = options[:plugin]
11
+ end
12
+
13
+ def execute(filename, options = {})
14
+ if command && parser
15
+ output = `#{command} #{filename} 2>/dev/null`
16
+ exit_code = $?.exitstatus
17
+ parser.parse(output, exit_code, options)
18
+ elsif plugin
19
+ plugin.new(filename).run(options)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ module Lintrunner
2
+ module GitHelpers
3
+ def git_common_ancestor
4
+ git.merge_base('master', 'HEAD')
5
+ end
6
+
7
+ def git_changeset
8
+ git.diff(git_common_ancestor, 'HEAD', context_lines: 1000)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module Lintrunner
2
+ class Message
3
+
4
+ attr_accessor :filename, :line, :name, :description
5
+
6
+ def initialize(filename:, line:, name:, description:)
7
+ self.filename = filename
8
+ self.line = line
9
+ self.name = name
10
+ self.description = description
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,78 @@
1
+ require "optparse"
2
+
3
+ module Lintrunner
4
+ class Options
5
+
6
+ attr_reader :options
7
+
8
+ def test; require 'pry'; binding.pry;end
9
+ def initialize
10
+ @options = {}
11
+ @option_parser = OptionParser.new do |opts|
12
+ add_banner(opts)
13
+ add_config_option(opts)
14
+ add_context_option(opts)
15
+ add_include_path_option(opts)
16
+ add_reporter_option(opts)
17
+ end
18
+ end
19
+
20
+ def parse(args)
21
+ @option_parser.parse!(args)
22
+ add_defaults
23
+ options[:path] = args.first if args.first
24
+ options
25
+ end
26
+
27
+ private
28
+
29
+ def add_defaults
30
+ options[:config] ||= ".lintrunner_config"
31
+ options[:context] ||= Dir.pwd
32
+ options[:include_paths] = Array(options[:include_paths]) << options[:context]
33
+ options[:include_paths].uniq!
34
+ options[:reporter] ||= "text"
35
+ options[:path] = Dir.pwd
36
+ end
37
+
38
+ def add_banner(opts)
39
+ opts.banner = unindent(<<-BANNER)
40
+ Run multiple linters with various runners
41
+ Usage: #{opts.program_name} [options] [path]
42
+ BANNER
43
+ end
44
+
45
+ def add_config_option(opts)
46
+ message = "the configuration file for lintrunner (default: .lintrunner_config)"
47
+ opts.on("-c", "--config config", message, String) do |config|
48
+ self.options[:config] = config
49
+ end
50
+ end
51
+
52
+ def add_context_option(opts)
53
+ message = "the path on which lintrunner will execute in (default: current path)"
54
+ opts.on("-x", "--context path", message, String) do |path|
55
+ self.options[:context] = Pathname.new(path).realpath.to_s
56
+ end
57
+ end
58
+
59
+ def add_include_path_option(opts)
60
+ message = "the paths to add to load paths (the context is in the load path)"
61
+ opts.on("--include_path path1,...", message, Array) do |paths|
62
+ self.options[:include_paths] = paths
63
+ end
64
+ end
65
+
66
+ def add_reporter_option(opts)
67
+ message = "the reporter that lintrunner uses to report results"
68
+ opts.on("--reporter reporter", message, String) do |reporter|
69
+ self.options[:reporter] = reporter
70
+ end
71
+ end
72
+
73
+ def unindent(str)
74
+ str.gsub(/^#{str.scan(/^[ ]+(?=\S)/).min}/, "")
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,19 @@
1
+ # Parsers are responsible for parsing the output of a linter command and returning an array of
2
+ # Lintrunner::Message for each warning produced
3
+ module Lintrunner
4
+ module Parser
5
+ class Base
6
+
7
+ # Parse the output of a linter so that lintrunner can understand what the warnings are
8
+ # @param [String] output The output of running the linter command
9
+ # @param [Integer] exit_code The exit code of the linter command
10
+ # @param [Hash] options
11
+ # @option opts [String] :filename Allow the caller to set the filename of the results
12
+ # @return [Array<Lintrunner::Message>] An array of warnings produced by the linter command
13
+ def parse(output, exit_code, options = {})
14
+ raise "Parser must implement #parser method"
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ module Lintrunner
2
+ module Parser
3
+ class Eslint < Base
4
+
5
+ # Example output of eslint (using the compact formatter):
6
+ # app/javascripts/delivery_page/components/add_guests.js: line 256, col 5, Error - Missing semicolon. (semi)
7
+ # app/javascripts/delivery_page/components/add_guests.js: line 277, col 18, Error - Props should be sorted alphabetically (react/jsx-sort-props)
8
+ def parse(output, exit_code, options = {})
9
+ return [] unless exit_code == 1
10
+
11
+ messages = []
12
+ output.each_line do |line|
13
+
14
+ match = /(?<filename>.*): line (?<line>\d*), .*Error - (?<description>.*) \((?<name>.*)\)/.match(line)
15
+ if match
16
+ messages << Lintrunner::Message.new(
17
+ filename: options[:filename] || match[:filename],
18
+ line: match["line"].to_i,
19
+ name: match["name"],
20
+ description: match["description"])
21
+ end
22
+ end
23
+ messages
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,57 @@
1
+ module Lintrunner
2
+ module Parser
3
+ class Rubocop
4
+
5
+ # Example output of rubocop (using the json formatter):
6
+ # {
7
+ # "metadata": {
8
+ # "rubocop_version": "0.32.1",
9
+ # "ruby_engine": "ruby",
10
+ # "ruby_version": "2.1.1",
11
+ # "ruby_patchlevel": "76",
12
+ # "ruby_platform": "x86_64-darwin14.0"
13
+ # },
14
+ # "files": [
15
+ # {
16
+ # "path": "lib\/lintrunner\/options.rb",
17
+ # "offenses": [
18
+ # {
19
+ # "severity": "warning",
20
+ # "message": "Remove debugger entry point `binding.pry`.",
21
+ # "cop_name": "Lint\/Debugger",
22
+ # "corrected": null,
23
+ # "location": {
24
+ # "line": 73,
25
+ # "column": 22,
26
+ # "length": 11
27
+ # }
28
+ # }
29
+ # ]
30
+ # }
31
+ # ],
32
+ # "summary": {
33
+ # "offense_count": 1,
34
+ # "target_file_count": 1,
35
+ # "inspected_file_count": 1
36
+ # }
37
+ # }
38
+ def parse(output, exit_code, options = {})
39
+ return [] unless exit_code == 1
40
+
41
+ messages = []
42
+ JSON.parse(output)["files"].each do |file_results|
43
+ filename = file_results["path"]
44
+ file_results["offenses"].each do |lint|
45
+ messages << Lintrunner::Message.new(
46
+ filename: options[:filename] || filename,
47
+ line: lint["location"]["line"],
48
+ name: lint["cop_name"],
49
+ description: lint["message"])
50
+ end
51
+ end
52
+ messages
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,36 @@
1
+ module Lintrunner
2
+ module Parser
3
+ class SCSSLint
4
+
5
+ # Example output of scss-lint (using the json formatter):
6
+ # {
7
+ # "app/styles/pages/delivery/_add_guests.scss": [
8
+ # {
9
+ # "line": 9,
10
+ # "column": 14,
11
+ # "length": 16,
12
+ # "severity": "warning",
13
+ # "reason": "Shorthand form for property `padding` should be written more concisely as `32px 70px 0` instead of `32px 70px 0 70px`",
14
+ # "linter": "Shorthand"
15
+ # }
16
+ # ]
17
+ # }
18
+ def parse(output, exit_code, options = {})
19
+ return [] unless exit_code == 1
20
+
21
+ messages = []
22
+ JSON.parse(output).each do |filename, lints|
23
+ lints.each do |lint|
24
+ messages << Lintrunner::Message.new(
25
+ filename: options[:filename] || filename,
26
+ line: lint["line"],
27
+ name: lint["linter"],
28
+ description: lint["reason"])
29
+ end
30
+ end
31
+ messages
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ module Lintrunner
2
+ module Plugin
3
+
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ # Reporters are responsible for generating the output of lintrunner
2
+ # Default behavior is noop for #start, #report, and #finish
3
+ module Lintrunner
4
+ module Reporter
5
+ class Base
6
+
7
+ attr_reader :path
8
+
9
+ def initialize(path)
10
+ @path = path
11
+ end
12
+
13
+ # Called when a lintrunner is started
14
+ # @param [String] name The name of the lintrunner (ex. "binding.pry check")
15
+ def start(name); end
16
+
17
+ # Called when a message is discovered from the lintrunner
18
+ # @param [Lintrunner::Message] message
19
+ def report(message); end
20
+
21
+ # Called when the lintrunner is finished executing
22
+ # @param [Array<Lintrunner::Message>] messages
23
+ def finish(messages); end
24
+
25
+ # The description of this reporter. Used for CLI when listing possible reporters
26
+ def description; end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ module Lintrunner
2
+ module Reporter
3
+ class Context < Text
4
+
5
+ def report(message)
6
+ puts "#{location(message)} #{message.description} #{message_name(message)}"
7
+ puts context(message)
8
+ puts
9
+ end
10
+
11
+ private
12
+
13
+ def context(message)
14
+ lineno = message.line - 1
15
+ start = lineno > 2 ? lineno - 3 : 0
16
+ range = start..(message.line + 2)
17
+ lines = ::File.readlines(::File.join(path, message.filename))[range]
18
+ message_index = lineno - start
19
+ lines[message_index] = lines[message_index].color(:red)
20
+ lines.join
21
+ end
22
+ end
23
+ end
24
+ end