lint_trappings 0.1.0

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.
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