lint_trappings 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +21 -0
  3. data/lib/lint_trappings.rb +17 -0
  4. data/lib/lint_trappings/application.rb +138 -0
  5. data/lib/lint_trappings/arguments_parser.rb +145 -0
  6. data/lib/lint_trappings/cli.rb +61 -0
  7. data/lib/lint_trappings/command/base.rb +36 -0
  8. data/lib/lint_trappings/command/display_documentation.rb +65 -0
  9. data/lib/lint_trappings/command/display_formatters.rb +14 -0
  10. data/lib/lint_trappings/command/display_help.rb +8 -0
  11. data/lib/lint_trappings/command/display_linters.rb +24 -0
  12. data/lib/lint_trappings/command/display_version.rb +14 -0
  13. data/lib/lint_trappings/command/scan.rb +19 -0
  14. data/lib/lint_trappings/configuration.rb +94 -0
  15. data/lib/lint_trappings/configuration_loader.rb +98 -0
  16. data/lib/lint_trappings/configuration_resolver.rb +49 -0
  17. data/lib/lint_trappings/document.rb +45 -0
  18. data/lib/lint_trappings/errors.rb +127 -0
  19. data/lib/lint_trappings/executable.rb +26 -0
  20. data/lib/lint_trappings/file_finder.rb +171 -0
  21. data/lib/lint_trappings/formatter/base.rb +67 -0
  22. data/lib/lint_trappings/formatter/checkstyle.rb +34 -0
  23. data/lib/lint_trappings/formatter/default.rb +99 -0
  24. data/lib/lint_trappings/formatter/json.rb +62 -0
  25. data/lib/lint_trappings/formatter_forwarder.rb +23 -0
  26. data/lib/lint_trappings/formatter_loader.rb +45 -0
  27. data/lib/lint_trappings/lint.rb +37 -0
  28. data/lib/lint_trappings/linter.rb +182 -0
  29. data/lib/lint_trappings/linter_configuration_validator.rb +42 -0
  30. data/lib/lint_trappings/linter_loader.rb +44 -0
  31. data/lib/lint_trappings/linter_plugin.rb +35 -0
  32. data/lib/lint_trappings/linter_selector.rb +120 -0
  33. data/lib/lint_trappings/location.rb +39 -0
  34. data/lib/lint_trappings/output.rb +118 -0
  35. data/lib/lint_trappings/preprocessor.rb +41 -0
  36. data/lib/lint_trappings/rake_task.rb +145 -0
  37. data/lib/lint_trappings/report.rb +58 -0
  38. data/lib/lint_trappings/runner.rb +161 -0
  39. data/lib/lint_trappings/spec.rb +12 -0
  40. data/lib/lint_trappings/spec/directory_helpers.rb +22 -0
  41. data/lib/lint_trappings/spec/indentation_helpers.rb +7 -0
  42. data/lib/lint_trappings/spec/matchers/report_lint_matcher.rb +169 -0
  43. data/lib/lint_trappings/spec/shared_contexts/linter_shared_context.rb +35 -0
  44. data/lib/lint_trappings/utils.rb +123 -0
  45. data/lib/lint_trappings/version.rb +4 -0
  46. metadata +117 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e033fd62166d774b4a18cc1c1a05713f194c22b7
4
+ data.tar.gz: 95160b22578c977eb2ed213c1ffd9ed665b5f4cd
5
+ SHA512:
6
+ metadata.gz: 5d917e31b883e44ca39cf96f02190242a321801bb1d72ca72643018930f07e83d27bbc60193a88d92c166cc257b94a33af381fe1b39a488066fef7c115627f22
7
+ data.tar.gz: b6134bc6b4559a063ba496fae837517e5c8f36fa46ea7f2a2f07a85944f5376151136291e909fa6cc95fe715b43264e7179dc77f97c01607bbb88202ea7c879e
@@ -0,0 +1,21 @@
1
+ LintTrappings is released under the MIT license.
2
+
3
+ > Copyright (c) 2016 Shane da Silva. http://shane.io
4
+ >
5
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ > of this software and associated documentation files (the "Software"), to deal
7
+ > in the Software without restriction, including without limitation the rights
8
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ > copies of the Software, and to permit persons to whom the Software is
10
+ > furnished to do so, subject to the following conditions:
11
+ >
12
+ > The above copyright notice and this permission notice shall be included in
13
+ > all copies or substantial portions of the Software.
14
+ >
15
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ > SOFTWARE.
@@ -0,0 +1,17 @@
1
+ # Load all modules necessary to work with the framework.
2
+ # Ordering here can be important depending on class references in each module.
3
+
4
+ require 'lint_trappings/errors'
5
+ require 'lint_trappings/configuration'
6
+ require 'lint_trappings/utils'
7
+ require 'lint_trappings/file_finder'
8
+ require 'lint_trappings/output'
9
+ require 'lint_trappings/document'
10
+ require 'lint_trappings/location'
11
+ require 'lint_trappings/lint'
12
+ require 'lint_trappings/linter'
13
+ require 'lint_trappings/linter_loader'
14
+ require 'lint_trappings/linter_selector'
15
+ require 'lint_trappings/report'
16
+ require 'lint_trappings/runner'
17
+ require 'lint_trappings/application'
@@ -0,0 +1,138 @@
1
+ require 'lint_trappings/errors'
2
+ require 'lint_trappings/configuration_loader'
3
+ require 'lint_trappings/configuration_resolver'
4
+
5
+ module LintTrappings
6
+ # Linter application superclass.
7
+ #
8
+ # Implementors will subclass this and specify a number of high-level
9
+ # configuration options which will help the class run your custom application.
10
+ #
11
+ # @abstract
12
+ class Application
13
+ # Define an application configuration attribute.
14
+ #
15
+ # This is intended to be used to make specifying the configuration for a
16
+ # LintTrappings application easy. It defines a class instance variable which is
17
+ # specified in the body of the class itself via DSL-like method call, and
18
+ # also defines a method so the value can be obtained from a single instance.
19
+ def self.class_attribute(attr_name)
20
+ # Define DSL getter/setter
21
+ metaclass = (class << self; self; end)
22
+ metaclass.instance_eval do
23
+ define_method(attr_name) do |*args|
24
+ if args.any?
25
+ instance_variable_set(:"@#{attr_name}", args.first)
26
+ else
27
+ value = instance_variable_get(:"@#{attr_name}")
28
+
29
+ if value.nil?
30
+ raise ApplicationConfigurationError,
31
+ "`#{attr_name}` class attribute must be defined in #{self}!"
32
+ end
33
+
34
+ value
35
+ end
36
+ end
37
+ end
38
+
39
+ # Define method on the class
40
+ define_method(attr_name) do
41
+ self.class.send(attr_name)
42
+ end
43
+ end
44
+
45
+ # @return [String] Proper name of this application
46
+ class_attribute :name
47
+
48
+ # @return [String] Name of the application executable
49
+ class_attribute :executable_name
50
+
51
+ # @return [String] Application version
52
+ class_attribute :version
53
+
54
+ # @return [LintTrappings::Configuration] Base configuration which all other
55
+ # configurations extend (can be empty if desired). This should be the same
56
+ # class as the configuration_class attribute.
57
+ class_attribute :base_configuration
58
+
59
+ # @return [String] Configuration file names to look for, in order of
60
+ # precedence (first one found wins)
61
+ class_attribute :configuration_file_names
62
+
63
+ # @return [String] List of file extensions the application can lint
64
+ class_attribute :file_extensions
65
+
66
+ # @return [String] URL of the application's home page
67
+ class_attribute :home_url
68
+
69
+ # @return [String] URL of the application's issue and bug reports page
70
+ class_attribute :issues_url
71
+
72
+ # @return [String] Directory prefix where gem stores built-in linters
73
+ class_attribute :linters_directory
74
+
75
+ # @return [Class] Base class of all linters for this application
76
+ class_attribute :linter_base_class
77
+
78
+ # @return [Class] Class to use when loading/parsing documents
79
+ class_attribute :document_class
80
+
81
+ # @param output [LintTrappings::Output]
82
+ def initialize(output)
83
+ @output = output
84
+ end
85
+
86
+ def run(options = {})
87
+ @output.color_enabled = options.fetch(:color, @output.tty?)
88
+ config = load_configuration(options)
89
+
90
+ command = create_command(options[:command]).new(self, config, options, @output)
91
+ command.run
92
+ end
93
+
94
+ private
95
+
96
+ def create_command(command)
97
+ raise InvalidCommandError,
98
+ '`command` option must be specified!' unless command
99
+
100
+ command = command.to_s
101
+
102
+ require 'lint_trappings/command/base'
103
+ begin
104
+ require "lint_trappings/command/#{Utils.snake_case(command)}"
105
+ rescue LoadError, SyntaxError => ex
106
+ raise InvalidCommandError,
107
+ "Unable to load command '#{command}': #{ex.message}"
108
+ end
109
+
110
+ Command.const_get(Utils.camel_case(command))
111
+ end
112
+
113
+ # Loads the application configuration.
114
+ #
115
+ # @param options [Hash]
116
+ #
117
+ # @return [LintTrappings::Configuration]
118
+ def load_configuration(options)
119
+ config_loader = ConfigurationLoader.new(self)
120
+
121
+ config =
122
+ if options[:config_file]
123
+ config_loader.load_file(options[:config_file])
124
+ else
125
+ begin
126
+ config_loader.load(working_directory: Dir.pwd)
127
+ rescue NoConfigurationFileError
128
+ base_configuration
129
+ end
130
+ end
131
+
132
+ config = ConfigurationResolver.new(config_loader).resolve(config, options)
133
+
134
+ # Always extend the base/default configuration
135
+ base_configuration.merge(config)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,145 @@
1
+ require 'optparse'
2
+
3
+ module LintTrappings
4
+ # Handles option parsing for the command line application.
5
+ class ArgumentsParser
6
+ def initialize(application)
7
+ @application = application
8
+ end
9
+
10
+ # Parses command line options into an options hash.
11
+ #
12
+ # @param args [Array<String>] arguments passed via the command line
13
+ #
14
+ # @return [Hash] parsed options
15
+ def parse(args)
16
+ @options = {}
17
+ @options[:command] = :scan # Default command is to scan for lints
18
+
19
+ OptionParser.new do |parser|
20
+ parser.banner = "Usage: #{@application.executable_name} [options] [file1, file2, ...]"
21
+
22
+ add_linter_options parser
23
+ add_file_options parser
24
+ add_misc_options parser
25
+ add_info_options parser
26
+ end.parse!(args)
27
+
28
+ # Any remaining arguments are assumed to be files that should be linted
29
+ @options[:included_paths] = args
30
+
31
+ @options
32
+ rescue OptionParser::InvalidOption => ex
33
+ raise InvalidCliOptionError,
34
+ "#{ex.message}\nRun `#{@application.executable_name} --help` to " \
35
+ 'see a list of available options.'
36
+ end
37
+
38
+ private
39
+
40
+ # Register file-related flags.
41
+ def add_file_options(parser)
42
+ parser.on('-c', '--config config-file', String,
43
+ 'Specify which configuration file you want to use') do |conf_file|
44
+ @options[:config_file] = conf_file
45
+ end
46
+
47
+ parser.on('-e', '--exclude-path file', String,
48
+ 'List of file paths to exclude') do |file_path|
49
+ (@options[:excluded_paths] ||= []) << file_path
50
+ end
51
+
52
+ parser.on('-f', '--format formatter-name', String,
53
+ 'Specify which output format you want') do |formatter_name|
54
+ (@options[:formatters] ||= []) << {
55
+ formatter_name => :stdout,
56
+ }
57
+ end
58
+
59
+ parser.on('-o', '--out output-file-path', String,
60
+ 'Specify a file to write output to') do |file_path|
61
+ if @options[:formatters]
62
+ # Change the last specified formatter to output to the given file
63
+ last_formatter = @options[:formatters].last
64
+ last_formatter[last_formatter.keys.first] = file_path
65
+ else
66
+ # Otherwise if no formatters have been specified yet, set the default
67
+ # formatter to write to the given file
68
+ @options[:formatters] << { 'Default' => file_path }
69
+ end
70
+ end
71
+
72
+ parser.on('--stdin-file-path file-path', String,
73
+ 'Specify the path name for the file passed via STDIN') do |file_path|
74
+ @options[:stdin] = STDIN
75
+ @options[:stdin_file_path] = file_path
76
+ end
77
+
78
+ parser.on('-r', '--require require-path', String,
79
+ 'Specify a path to `require`') do |require_path|
80
+ (@options[:require_paths] ||= []) << require_path
81
+ end
82
+ end
83
+
84
+ # Register linter-related flags.
85
+ def add_linter_options(parser)
86
+ parser.on('-i', '--include-linter linter', String,
87
+ 'Specify which linters you want to run ' \
88
+ '(overrides those in your configuration)') do |linter|
89
+ (@options[:included_linters] ||= []).concat(linter.split(/\s*,\s*/))
90
+ end
91
+
92
+ parser.on('-x', '--exclude-linter linter', String,
93
+ "Specify which linters you don't want to run " \
94
+ '(in addition to those disabled by your configuration)') do |linter|
95
+ (@options[:excluded_linters] ||= []).concat(linter.split(/\s*,\s*/))
96
+ end
97
+ end
98
+
99
+ def add_misc_options(parser)
100
+ parser.on('-C', '--concurrency workers', Integer,
101
+ 'Specify the number of concurrent workers you want') do |workers|
102
+ @options[:concurrency] = workers
103
+ end
104
+ end
105
+
106
+ # Register informational flags.
107
+ def add_info_options(parser)
108
+ parser.on('--show-linters',
109
+ 'Display available linters and whether or not they are enabled') do
110
+ @options[:command] = :display_linters
111
+ end
112
+
113
+ parser.on('--show-formatters', 'Display available formatters') do
114
+ @options[:command] = :display_formatters
115
+ end
116
+
117
+ parser.on('--show-docs [linter-name]', '--show-documentation [linter-name]') do |linter_name|
118
+ @options[:command] = :display_documentation
119
+ @options[:linter] = linter_name
120
+ end
121
+
122
+ parser.on('--[no-]color', 'Force output to be colorized') do |color|
123
+ @options[:color] = color
124
+ end
125
+
126
+ parser.on('-d', '--debug', 'Enable debug mode for more verbose output') do
127
+ @options[:debug] = true
128
+ end
129
+
130
+ parser.on_tail('-h', '--help', 'Display help documentation') do
131
+ @options[:command] = :display_help
132
+ @options[:help_message] = parser.help
133
+ end
134
+
135
+ parser.on_tail('-v', '--version', 'Display version') do
136
+ @options[:command] = :display_version
137
+ end
138
+
139
+ parser.on_tail('-V', '--verbose-version', 'Display verbose version information') do
140
+ @options[:command] = :display_version
141
+ @options[:verbose_version] = true
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,61 @@
1
+ require 'lint_trappings'
2
+ require 'lint_trappings/arguments_parser'
3
+
4
+ module LintTrappings
5
+ # Command line application interface.
6
+ class Cli
7
+ # @param application [LintTrappings::Application]
8
+ # @param output [LintTrappings::Output] stderr stream
9
+ def initialize(application, output)
10
+ @application = application
11
+ @output = output
12
+ end
13
+
14
+ # Parses the given command line arguments and executes appropriate logic
15
+ # based on those arguments.
16
+ #
17
+ # @param args [Array<String>] command line arguments
18
+ #
19
+ # @return [Integer] exit status code
20
+ def run(args)
21
+ options = ArgumentsParser.new(@application).parse(args)
22
+ @application.run(options)
23
+ 0 # OK
24
+ rescue ScanWarned, ScanFailed => ex
25
+ # Special errors which we don't want to display, but do want their exit status
26
+ ex.exit_status
27
+ rescue => ex
28
+ handle_exception(ex)
29
+ end
30
+
31
+ private
32
+
33
+ # Returns an appropriate error code for the specified exception, and outputs
34
+ # a message if necessary.
35
+ def handle_exception(ex)
36
+ if ex.is_a?(LintTrappings::LintTrappingsError) && ex.exit_status != 70
37
+ @output.error ex.message
38
+ ex.exit_status
39
+ else
40
+ print_unexpected_exception(ex)
41
+ ex.respond_to?(:exit_status) ? ex.exit_status : 70
42
+ end
43
+ end
44
+
45
+ # Outputs the backtrace of an exception with instructions on how to report
46
+ # the issue.
47
+ def print_unexpected_exception(ex)
48
+ @output.bold_error ex.message
49
+ @output.error ex.backtrace.join("\n")
50
+ @output.warning 'Report this bug at ', false
51
+ @output.info @application.issues_url
52
+ @output.newline
53
+ @output.success 'To help fix this issue, please include:'
54
+ @output.puts '- The above stack trace'
55
+ @output.print "- #{@application.name} version: "
56
+ @output.info @application.version
57
+ @output.print '- Ruby version: '
58
+ @output.info RUBY_VERSION
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,36 @@
1
+ module LintTrappings::Command
2
+ # Abstract base class of all commands.
3
+ #
4
+ # @abstract
5
+ class Base
6
+ # @param application [LintTrappings::Application]
7
+ # @param config [LintTrappings::Configuration]
8
+ # @param options [Hash]
9
+ # @param output [LintTrappings::Output]
10
+ def initialize(application, config, options, output)
11
+ @application = application
12
+ @config = config
13
+ @options = options
14
+ @output = output
15
+ end
16
+
17
+ # Runs the command.
18
+ def run
19
+ raise NotImplementedError, 'Define `execute` in `Command::Base` subclass'
20
+ end
21
+
22
+ private
23
+
24
+ # @return [LintTrappings::Application]
25
+ attr_reader :application
26
+
27
+ # @return [LintTrappings::Configuration]
28
+ attr_reader :config
29
+
30
+ # @return [Hash]
31
+ attr_reader :options
32
+
33
+ # @return [LintTrappings::Output]
34
+ attr_reader :output
35
+ end
36
+ end
@@ -0,0 +1,65 @@
1
+ require 'terminal-table'
2
+
3
+ module LintTrappings::Command
4
+ # Displays documentation for the specified linter
5
+ class DisplayDocumentation < Base
6
+ def run
7
+ LintTrappings::LinterLoader.new(application, config).load(options)
8
+
9
+ output.info 'Linter Documentation'
10
+ output.info '--------------------'
11
+
12
+ linter_classes(options).sort_by(&:canonical_name).each do |linter_class|
13
+ display_linter_doc(linter_class)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def display_linter_doc(linter_class)
20
+ output.newline
21
+ output.notice linter_class.canonical_name
22
+ output.puts linter_class.description
23
+ output.newline
24
+
25
+ return unless linter_class.options.any?
26
+
27
+ table = Terminal::Table.new do |t|
28
+ t << %w[Option Description Type Default]
29
+
30
+ t.add_separator
31
+
32
+ linter_class.options.each do |option_name, option_spec|
33
+ t << [
34
+ option_name,
35
+ option_spec[:description],
36
+ option_spec[:type],
37
+ display_value(option_spec[:default]),
38
+ ]
39
+ end
40
+ end
41
+
42
+ output.puts table.to_s
43
+ end
44
+
45
+ def display_value(value)
46
+ if value.is_a?(Array)
47
+ value.map { |v| "- #{v}" }.join("\n")
48
+ else
49
+ value
50
+ end
51
+ end
52
+
53
+ def linter_classes(options)
54
+ if options[:linter]
55
+ begin
56
+ [application.linter_base_class.const_get(options[:linter])]
57
+ rescue NameError
58
+ raise NoSuchLinter, "No linter named #{options[:linter]} exists!"
59
+ end
60
+ else
61
+ application.linter_base_class.descendants
62
+ end
63
+ end
64
+ end
65
+ end