rubocop 1.90.0 → 1.91.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.
- checksums.yaml +4 -4
- data/config/default.yml +238 -10
- data/config/obsoletion.yml +9 -0
- data/lib/rubocop/changed_files.rb +112 -0
- data/lib/rubocop/cli/command/execute_runner.rb +21 -1
- data/lib/rubocop/cli.rb +9 -3
- data/lib/rubocop/comment_config/directive_range.rb +3 -1
- data/lib/rubocop/comment_config/disable_next.rb +49 -3
- data/lib/rubocop/comment_config/push_pop.rb +55 -0
- data/lib/rubocop/comment_config.rb +21 -51
- data/lib/rubocop/config.rb +10 -0
- data/lib/rubocop/config_loader.rb +12 -4
- data/lib/rubocop/config_loader_resolver.rb +49 -7
- data/lib/rubocop/config_obsoletion/cop_rule.rb +2 -1
- data/lib/rubocop/config_obsoletion/removed_cop.rb +8 -0
- data/lib/rubocop/config_obsoletion.rb +1 -1
- data/lib/rubocop/config_validator.rb +18 -3
- data/lib/rubocop/cop/badge.rb +1 -1
- data/lib/rubocop/cop/base.rb +15 -3
- data/lib/rubocop/cop/correctors.rb +2 -2
- data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
- data/lib/rubocop/cop/generator.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
- data/lib/rubocop/cop/layout/argument_alignment.rb +2 -0
- data/lib/rubocop/cop/layout/comment_indentation.rb +8 -5
- data/lib/rubocop/cop/layout/else_alignment.rb +1 -0
- data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
- data/lib/rubocop/cop/layout/first_array_element_indentation.rb +17 -4
- data/lib/rubocop/cop/layout/first_parameter_indentation.rb +11 -0
- data/lib/rubocop/cop/layout/hash_alignment.rb +6 -5
- data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
- data/lib/rubocop/cop/layout/line_length.rb +19 -1
- data/lib/rubocop/cop/layout/space_around_block_parameters.rb +14 -3
- data/lib/rubocop/cop/layout/space_around_operators.rb +5 -1
- data/lib/rubocop/cop/lint/argument_mismatch.rb +14 -4
- data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -0
- data/lib/rubocop/cop/lint/cop_directive_syntax.rb +88 -14
- data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
- data/lib/rubocop/cop/lint/empty_block.rb +1 -1
- data/lib/rubocop/cop/lint/literal_as_condition.rb +8 -2
- data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +185 -0
- data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -1
- data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
- data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +17 -1
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +30 -16
- data/lib/rubocop/cop/lint.rb +1 -0
- data/lib/rubocop/cop/migration/department_name.rb +1 -1
- data/lib/rubocop/cop/mixin/alignment.rb +1 -1
- data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +11 -1
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +6 -2
- data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
- data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +12 -1
- data/lib/rubocop/cop/mixin.rb +2 -2
- data/lib/rubocop/cop/naming/block_forwarding.rb +4 -1
- data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
- data/lib/rubocop/cop/registry.rb +9 -1
- data/lib/rubocop/cop/severity.rb +12 -0
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -2
- data/lib/rubocop/cop/style/arguments_forwarding.rb +2 -2
- data/lib/rubocop/cop/style/case_like_if.rb +3 -3
- data/lib/rubocop/cop/style/constant_visibility.rb +6 -2
- data/lib/rubocop/cop/style/data_inheritance.rb +14 -0
- data/lib/rubocop/cop/style/directive_scope.rb +154 -21
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +53 -9
- data/lib/rubocop/cop/style/documentation_method.rb +1 -9
- data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
- data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
- data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
- data/lib/rubocop/cop/style/empty_method.rb +12 -2
- data/lib/rubocop/cop/style/endless_method.rb +31 -12
- data/lib/rubocop/cop/style/file_null.rb +2 -2
- data/lib/rubocop/cop/style/float_division.rb +10 -16
- data/lib/rubocop/cop/style/for.rb +5 -0
- data/lib/rubocop/cop/style/format_string_token.rb +1 -1
- data/lib/rubocop/cop/style/guard_clause.rb +4 -0
- data/lib/rubocop/cop/style/hash_lookup_method.rb +3 -0
- data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/nested_modifier.rb +9 -4
- data/lib/rubocop/cop/style/nil_comparison.rb +36 -8
- data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
- data/lib/rubocop/cop/style/non_nil_check.rb +18 -2
- data/lib/rubocop/cop/style/not.rb +3 -1
- data/lib/rubocop/cop/style/numeric_literal_prefix.rb +11 -1
- data/lib/rubocop/cop/style/one_line_conditional.rb +1 -1
- data/lib/rubocop/cop/style/parallel_assignment.rb +1 -1
- data/lib/rubocop/cop/style/parentheses_around_condition.rb +6 -1
- data/lib/rubocop/cop/style/redundant_condition.rb +4 -4
- data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +10 -1
- data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
- data/lib/rubocop/cop/style/redundant_parentheses.rb +11 -1
- data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +6 -5
- data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +6 -6
- data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
- data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
- data/lib/rubocop/cop/style/struct_inheritance.rb +14 -0
- data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
- data/lib/rubocop/cop/style.rb +0 -1
- data/lib/rubocop/cop/util.rb +1 -1
- data/lib/rubocop/cops_documentation_generator.rb +23 -2
- data/lib/rubocop/directive_comment.rb +37 -8
- data/lib/rubocop/formatter/disabled_config_formatter.rb +19 -1
- data/lib/rubocop/formatter/formatter_set.rb +2 -1
- data/lib/rubocop/formatter/github_actions_formatter.rb +6 -5
- data/lib/rubocop/formatter/html_formatter.rb +2 -2
- data/lib/rubocop/formatter/junit_formatter.rb +1 -1
- data/lib/rubocop/formatter/sarif_formatter.rb +181 -0
- data/lib/rubocop/formatter.rb +1 -0
- data/lib/rubocop/magic_comment.rb +27 -0
- data/lib/rubocop/options.rb +59 -1
- data/lib/rubocop/path_util.rb +1 -1
- data/lib/rubocop/plugin/loader.rb +1 -1
- data/lib/rubocop/rake_task.rb +1 -1
- data/lib/rubocop/remote_config.rb +4 -4
- data/lib/rubocop/result_cache.rb +16 -5
- data/lib/rubocop/runner.rb +64 -21
- data/lib/rubocop/unified_diff.rb +257 -0
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +4 -1
- metadata +8 -4
- data/lib/rubocop/cop/style/double_cop_disable_directive.rb +0 -46
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
# Lists the files that differ from a git revision, for `--changed`.
|
|
7
|
+
#
|
|
8
|
+
# Shelling out to `git` keeps RuboCop free of a version control dependency,
|
|
9
|
+
# and means the answer is exactly what git would give on the command line.
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
class ChangedFiles
|
|
13
|
+
DEFAULT_REVISION = 'HEAD'
|
|
14
|
+
FILESYSTEM_ENCODING = Encoding.find('filesystem')
|
|
15
|
+
|
|
16
|
+
def initialize(revision = nil)
|
|
17
|
+
@revision = revision || DEFAULT_REVISION
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Files that were added or modified since the revision, as absolute paths.
|
|
21
|
+
# Deleted files are left out - there is nothing left to inspect - and
|
|
22
|
+
# untracked files are included, since a file that is new to the working
|
|
23
|
+
# tree has changed as surely as one git already knows about.
|
|
24
|
+
#
|
|
25
|
+
# @return [Set<String>]
|
|
26
|
+
def paths
|
|
27
|
+
root = repository_root
|
|
28
|
+
|
|
29
|
+
(modified_paths + untracked_paths).to_set { |path| File.expand_path(path, root) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def repository_root
|
|
35
|
+
filesystem_path(git('rev-parse', '--show-toplevel').b.chomp)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def modified_paths
|
|
39
|
+
# `diff.relative` would make git report paths relative to the current
|
|
40
|
+
# directory rather than to the repository root.
|
|
41
|
+
split_paths(
|
|
42
|
+
git('-c', 'diff.relative=false', 'diff', '--name-only', '--diff-filter=d', '-z', @revision)
|
|
43
|
+
)
|
|
44
|
+
rescue Error
|
|
45
|
+
raise unless unborn_head?
|
|
46
|
+
|
|
47
|
+
# A repository without commits has nothing to diff against, and
|
|
48
|
+
# everything in it is untracked - which is what the caller wants to hear
|
|
49
|
+
# rather than an error about `HEAD` not resolving.
|
|
50
|
+
[]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def unborn_head?
|
|
54
|
+
return false unless @revision == DEFAULT_REVISION
|
|
55
|
+
|
|
56
|
+
_stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--verify', '--quiet', 'HEAD')
|
|
57
|
+
!status.success?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def untracked_paths
|
|
61
|
+
# `--full-name` anchors the paths to the repository root, the way the
|
|
62
|
+
# paths from `git diff` already are.
|
|
63
|
+
split_paths(git('ls-files', '--others', '--exclude-standard', '--full-name', '-z'))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The `-z` above is what makes these usable: without it git wraps any path
|
|
67
|
+
# that is not plain ASCII in quotes and octal escapes, and those files drop
|
|
68
|
+
# out of the comparison without a word.
|
|
69
|
+
def split_paths(output)
|
|
70
|
+
output.b.split("\0").reject(&:empty?).map { |path| filesystem_path(path) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# git hands back bytes, which Ruby tags with the encoding the process
|
|
74
|
+
# happens to have been started in - splitting those bytes as US-ASCII raises
|
|
75
|
+
# on any path that is not. They have to end up in the encoding `Dir.glob`
|
|
76
|
+
# uses, or a path with a non-ASCII character in it never matches the file
|
|
77
|
+
# list it is compared against.
|
|
78
|
+
#
|
|
79
|
+
# On POSIX those bytes are already the filesystem's own, so the encoding is
|
|
80
|
+
# simply corrected. Windows is the exception: git converts paths to UTF-8 on
|
|
81
|
+
# the way out, while Ruby reports file names in the local code page, so the
|
|
82
|
+
# two have to be reconciled.
|
|
83
|
+
def filesystem_path(path)
|
|
84
|
+
path = path.dup
|
|
85
|
+
|
|
86
|
+
return path.force_encoding(FILESYSTEM_ENCODING) unless Platform.windows?
|
|
87
|
+
|
|
88
|
+
path.force_encoding(Encoding::UTF_8).encode(FILESYSTEM_ENCODING)
|
|
89
|
+
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
|
|
90
|
+
# A name the local code page cannot represent. Nothing good is left to do
|
|
91
|
+
# with it, and dropping it would be worse than reporting it as-is.
|
|
92
|
+
path.force_encoding(FILESYSTEM_ENCODING)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def git(*args)
|
|
96
|
+
stdout, stderr, status = Open3.capture3('git', *args)
|
|
97
|
+
|
|
98
|
+
raise Error, git_error_message(stderr) unless status.success?
|
|
99
|
+
|
|
100
|
+
stdout
|
|
101
|
+
rescue Errno::ENOENT
|
|
102
|
+
raise Error, '--changed requires git to be installed and on your PATH.'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def git_error_message(stderr)
|
|
106
|
+
message = stderr.strip
|
|
107
|
+
message = 'git failed with no output on standard error.' if message.empty?
|
|
108
|
+
|
|
109
|
+
"--changed could not ask git which files changed: #{message}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -28,7 +28,16 @@ module RuboCop
|
|
|
28
28
|
all_passed || @options[:auto_gen_config]
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
if @options[:diff]
|
|
32
|
+
print_diffs(runner.diffs)
|
|
33
|
+
else
|
|
34
|
+
maybe_print_corrected_source
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# A diff means autocorrectable offenses are still in the working
|
|
38
|
+
# tree, so the run has not passed even though they were "corrected"
|
|
39
|
+
# in memory.
|
|
40
|
+
all_pass_or_excluded &&= runner.diffs.empty?
|
|
32
41
|
|
|
33
42
|
merge_todo_audit_status(runner_status(runner, all_pass_or_excluded))
|
|
34
43
|
end
|
|
@@ -119,6 +128,17 @@ module RuboCop
|
|
|
119
128
|
"#{Gem.loaded_specs['rubocop'].metadata['bug_tracker_uri']}\n"
|
|
120
129
|
end
|
|
121
130
|
|
|
131
|
+
def print_diffs(diffs)
|
|
132
|
+
return if diffs.empty?
|
|
133
|
+
# Integration tools own stdout when they ask for a machine-readable
|
|
134
|
+
# format, so the diff would only corrupt their input.
|
|
135
|
+
return if INTEGRATION_FORMATTERS.include?(@options[:format])
|
|
136
|
+
|
|
137
|
+
output = @options[:stderr] ? $stderr : $stdout
|
|
138
|
+
output.puts
|
|
139
|
+
diffs.each { |diff| output.print(diff) }
|
|
140
|
+
end
|
|
141
|
+
|
|
122
142
|
def maybe_print_corrected_source
|
|
123
143
|
# Integration tools (like RubyMine) expect to have only the JSON result
|
|
124
144
|
# when specifying JSON format. Similar HTML and JUnit are targeted as well.
|
data/lib/rubocop/cli.rb
CHANGED
|
@@ -13,7 +13,7 @@ module RuboCop
|
|
|
13
13
|
DEFAULT_PARALLEL_OPTIONS = %i[
|
|
14
14
|
color config debug display_style_guide display_time display_only_fail_level_offenses
|
|
15
15
|
display_only_failed editor_mode except extra_details fail_level fix_layout format formatters
|
|
16
|
-
ignore_disable_comments lint only only_guide_cops out require safe
|
|
16
|
+
ignore_disable_comments lint only only_guide_cops out preview require safe
|
|
17
17
|
autocorrect safe_autocorrect autocorrect_all
|
|
18
18
|
].freeze
|
|
19
19
|
|
|
@@ -183,12 +183,18 @@ module RuboCop
|
|
|
183
183
|
|
|
184
184
|
def set_options_to_config_loader
|
|
185
185
|
ConfigLoader.debug = @options[:debug]
|
|
186
|
-
ConfigLoader.disable_pending_cops = @options[:disable_pending_cops]
|
|
187
|
-
ConfigLoader.enable_pending_cops = @options[:enable_pending_cops]
|
|
188
186
|
ConfigLoader.ignore_parent_exclusion = @options[:ignore_parent_exclusion]
|
|
189
187
|
ConfigLoader.ignore_unrecognized_cops = @options[:ignore_unrecognized_cops]
|
|
188
|
+
set_cop_selection_options_to_config_loader
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Options that decide which cops run, as opposed to how configuration is loaded.
|
|
192
|
+
def set_cop_selection_options_to_config_loader
|
|
193
|
+
ConfigLoader.disable_pending_cops = @options[:disable_pending_cops]
|
|
194
|
+
ConfigLoader.enable_pending_cops = @options[:enable_pending_cops]
|
|
190
195
|
ConfigLoader.enabled_by_default = @options[:enable_all_cops]
|
|
191
196
|
ConfigLoader.disabled_by_default = @options[:disable_all_cops]
|
|
197
|
+
ConfigLoader.preview = @options[:preview]
|
|
192
198
|
end
|
|
193
199
|
|
|
194
200
|
def set_options_to_pending_cops_reporter
|
|
@@ -22,8 +22,10 @@ module RuboCop
|
|
|
22
22
|
CopAnalysis = Struct.new(:line_ranges, :start_line_number, :start_directive) do
|
|
23
23
|
# The open range (if any) closed at the given line, appended to the
|
|
24
24
|
# accumulated ranges, each remembering the directive that opened it.
|
|
25
|
+
# Closing above the opening line (e.g. a `pop` directly below a
|
|
26
|
+
# statement whose disable a `next +Cop` suspended) yields no range.
|
|
25
27
|
def close(line)
|
|
26
|
-
return line_ranges
|
|
28
|
+
return line_ranges if !start_line_number || line < start_line_number
|
|
27
29
|
|
|
28
30
|
line_ranges + [DirectiveRange.new(start_line_number, line, start_directive)]
|
|
29
31
|
end
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
class CommentConfig
|
|
5
|
-
# Handling of `disable-next`
|
|
6
|
-
# they apply to and tracking
|
|
5
|
+
# Handling of next-statement directives (`disable-next`, `todo-next` and
|
|
6
|
+
# `next`): computing the statement scope they apply to and tracking
|
|
7
|
+
# directives that attached to nothing.
|
|
7
8
|
# The scope is the whole statement starting on the next line bearing
|
|
8
9
|
# code. Comment-only lines between the directive and the code chain (so
|
|
9
10
|
# several directives can stack), while a blank line breaks the attachment.
|
|
10
11
|
# @api private
|
|
11
12
|
module DisableNext
|
|
12
|
-
#
|
|
13
|
+
# Next-statement directives that affect nothing: nothing follows
|
|
13
14
|
# them, or they sit at the end of a code line.
|
|
14
15
|
def detached_next_directives
|
|
15
16
|
cop_disabled_line_ranges # ensure the analysis that collects them ran
|
|
@@ -48,6 +49,51 @@ module RuboCop
|
|
|
48
49
|
analysis.start_line_number, analysis.start_directive)
|
|
49
50
|
end
|
|
50
51
|
|
|
52
|
+
# Applies a `next` directive: `-` cops are disabled for the attached
|
|
53
|
+
# statement (exactly like `disable-next`), `+` cops have any open
|
|
54
|
+
# disable suspended for it. The `resolved_cops` come pre-expanded and
|
|
55
|
+
# qualified, mirroring `push`.
|
|
56
|
+
def apply_next_directive(analyses, resolved_cops, directive)
|
|
57
|
+
bounds = statement_scope_after(directive.line_number)
|
|
58
|
+
return @detached_next_directives << directive unless bounds
|
|
59
|
+
|
|
60
|
+
range = DirectiveRange.new(bounds.begin, bounds.end, directive)
|
|
61
|
+
resolved_cops.each do |op, cops|
|
|
62
|
+
cops.each do |cop|
|
|
63
|
+
if op == '-'
|
|
64
|
+
add_next_range(analyses, cop, range)
|
|
65
|
+
else
|
|
66
|
+
suspend_disable(analyses, cop, bounds)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Applies an `enable-next` directive: every listed cop (departments
|
|
73
|
+
# and `all` included) has any open disable suspended for the attached
|
|
74
|
+
# statement.
|
|
75
|
+
def apply_enable_next(analyses, directive)
|
|
76
|
+
bounds = statement_scope_after(directive.line_number)
|
|
77
|
+
return @detached_next_directives << directive unless bounds
|
|
78
|
+
|
|
79
|
+
directive.cop_names.each do |cop_name|
|
|
80
|
+
suspend_disable(analyses, qualified_cop_name(cop_name), bounds)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Punches a statement-sized hole into the currently open disable of the
|
|
85
|
+
# cop (whether opened by a directive or injected for a config-disabled
|
|
86
|
+
# cop): the open range closes just above the statement and reopens
|
|
87
|
+
# right below it, still attributed to its opening directive. Aligned
|
|
88
|
+
# with `push`, a `+` for a cop that is not disabled is a no-op.
|
|
89
|
+
def suspend_disable(analyses, cop, bounds)
|
|
90
|
+
analysis = analyses[cop]
|
|
91
|
+
return unless analysis.start_line_number
|
|
92
|
+
|
|
93
|
+
analyses[cop] = CopAnalysis.new(analysis.close(bounds.begin - 1),
|
|
94
|
+
bounds.end + 1, analysis.start_directive)
|
|
95
|
+
end
|
|
96
|
+
|
|
51
97
|
def attached_code_line(directive_line)
|
|
52
98
|
((directive_line + 1)..processed_source.lines.size).each do |line|
|
|
53
99
|
return line unless comment_only_line?(line)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
class CommentConfig
|
|
5
|
+
# Handling of `push`/`pop` directives and their signed cop arguments:
|
|
6
|
+
# saving and restoring the per-cop analysis state, and resolving the
|
|
7
|
+
# `+`/`-` argument lists (shared with the `next` directive).
|
|
8
|
+
# @api private
|
|
9
|
+
module PushPop
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def resolve_push_cops(directive)
|
|
13
|
+
directive.push_args.transform_values do |names|
|
|
14
|
+
names.flat_map { |name| expand_cop_name(name) }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def expand_cop_name(name)
|
|
19
|
+
registry = Cop::Registry.global
|
|
20
|
+
cops = registry.department?(name) ? registry.names_for_department(name) : [name]
|
|
21
|
+
cops.map { |c| qualified_cop_name(c) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def apply_push(analyses, resolved_cops, directive)
|
|
25
|
+
resolved_cops.each do |op, cops|
|
|
26
|
+
cops.each { |cop| apply_cop_op(analyses, op, cop, directive) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def apply_cop_op(analyses, operation, cop, directive)
|
|
31
|
+
analysis = analyses[cop]
|
|
32
|
+
line = directive.line_number
|
|
33
|
+
if operation == '-' && !analysis.start_line_number
|
|
34
|
+
analyses[cop] = CopAnalysis.new(analysis.line_ranges, line, directive)
|
|
35
|
+
elsif operation == '+' && analysis.start_line_number
|
|
36
|
+
analyses[cop] = CopAnalysis.new(analysis.close(line), nil)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def pop_state(analyses, line)
|
|
41
|
+
restore_point = @stack.pop
|
|
42
|
+
(restore_point.keys | analyses.keys).each do |cop|
|
|
43
|
+
analyses[cop] = popped_analysis(analyses[cop], restore_point[cop], line)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def popped_analysis(current, restored, line)
|
|
48
|
+
ranges = current.close(line - 1)
|
|
49
|
+
return CopAnalysis.new(ranges, nil) unless restored&.start_line_number
|
|
50
|
+
|
|
51
|
+
CopAnalysis.new(ranges, line, restored.start_directive)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -6,6 +6,7 @@ module RuboCop
|
|
|
6
6
|
class CommentConfig
|
|
7
7
|
extend SimpleForwardable
|
|
8
8
|
include DisableNext
|
|
9
|
+
include PushPop
|
|
9
10
|
|
|
10
11
|
CONFIG_DISABLED_LINE_RANGE_MIN = -Float::INFINITY
|
|
11
12
|
|
|
@@ -75,8 +76,9 @@ module RuboCop
|
|
|
75
76
|
non_comment_token_line_numbers.none?(line_number)
|
|
76
77
|
end
|
|
77
78
|
|
|
78
|
-
# The names of the cops that are opted in by an `enable` comment directive
|
|
79
|
-
#
|
|
79
|
+
# The names of the cops that are opted in by an `enable` comment directive
|
|
80
|
+
# or a `+` argument of a `push`/`next` directive, used to mobilize cops
|
|
81
|
+
# disabled in the config on demand.
|
|
80
82
|
#
|
|
81
83
|
# @api private
|
|
82
84
|
# @return [Set<String>]
|
|
@@ -84,10 +86,11 @@ module RuboCop
|
|
|
84
86
|
@opt_in_cops ||= begin
|
|
85
87
|
cops = Set.new
|
|
86
88
|
each_directive do |directive|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
if directive.enabled?
|
|
90
|
+
cops.merge(directive.raw_cop_names) unless directive.all_cops?
|
|
91
|
+
elsif directive.push? || directive.next?
|
|
92
|
+
cops.merge(directive.signed_args.fetch('+', []))
|
|
93
|
+
end
|
|
91
94
|
end
|
|
92
95
|
cops
|
|
93
96
|
end
|
|
@@ -98,9 +101,7 @@ module RuboCop
|
|
|
98
101
|
def extra_enabled_comments_with_names(extras:, names:)
|
|
99
102
|
each_directive do |directive|
|
|
100
103
|
next unless comment_only_line?(directive.line_number)
|
|
101
|
-
|
|
102
|
-
# play no part in the disable/enable pairing.
|
|
103
|
-
next if directive.push? || directive.pop? || directive.disable_next?
|
|
104
|
+
next if self_closing_directive?(directive)
|
|
104
105
|
|
|
105
106
|
if directive.enabled_all?
|
|
106
107
|
handle_enable_all(directive, names, extras)
|
|
@@ -128,6 +129,10 @@ module RuboCop
|
|
|
128
129
|
pop_state(analyses, directive.line_number) if @stack.any?
|
|
129
130
|
elsif directive.disable_next?
|
|
130
131
|
apply_disable_next(analyses, directive)
|
|
132
|
+
elsif directive.next?
|
|
133
|
+
apply_next_directive(analyses, resolve_push_cops(directive), directive)
|
|
134
|
+
elsif directive.enable_next?
|
|
135
|
+
apply_enable_next(analyses, directive)
|
|
131
136
|
else
|
|
132
137
|
directive.cop_names.each do |cop_name|
|
|
133
138
|
cop_name = qualified_cop_name(cop_name)
|
|
@@ -144,48 +149,6 @@ module RuboCop
|
|
|
144
149
|
end
|
|
145
150
|
end
|
|
146
151
|
|
|
147
|
-
def resolve_push_cops(directive)
|
|
148
|
-
directive.push_args.transform_values do |names|
|
|
149
|
-
names.flat_map { |name| expand_cop_name(name) }
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def expand_cop_name(name)
|
|
154
|
-
registry = Cop::Registry.global
|
|
155
|
-
cops = registry.department?(name) ? registry.names_for_department(name) : [name]
|
|
156
|
-
cops.map { |c| qualified_cop_name(c) }
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def apply_push(analyses, resolved_cops, directive)
|
|
160
|
-
resolved_cops.each do |op, cops|
|
|
161
|
-
cops.each { |cop| apply_cop_op(analyses, op, cop, directive) }
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def apply_cop_op(analyses, operation, cop, directive)
|
|
166
|
-
analysis = analyses[cop]
|
|
167
|
-
line = directive.line_number
|
|
168
|
-
if operation == '-' && !analysis.start_line_number
|
|
169
|
-
analyses[cop] = CopAnalysis.new(analysis.line_ranges, line, directive)
|
|
170
|
-
elsif operation == '+' && analysis.start_line_number
|
|
171
|
-
analyses[cop] = CopAnalysis.new(analysis.close(line), nil)
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def pop_state(analyses, line)
|
|
176
|
-
restore_point = @stack.pop
|
|
177
|
-
(restore_point.keys | analyses.keys).each do |cop|
|
|
178
|
-
analyses[cop] = popped_analysis(analyses[cop], restore_point[cop], line)
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def popped_analysis(current, restored, line)
|
|
183
|
-
ranges = current.close(line - 1)
|
|
184
|
-
return CopAnalysis.new(ranges, nil) unless restored&.start_line_number
|
|
185
|
-
|
|
186
|
-
CopAnalysis.new(ranges, line, restored.start_directive)
|
|
187
|
-
end
|
|
188
|
-
|
|
189
152
|
def inject_disabled_cops_directives(analyses)
|
|
190
153
|
registry.disabled_names(config).each do |cop_name|
|
|
191
154
|
analyses[cop_name] = analyze_cop(
|
|
@@ -236,6 +199,13 @@ module RuboCop
|
|
|
236
199
|
end
|
|
237
200
|
end
|
|
238
201
|
|
|
202
|
+
# Push/pop and next-statement directives close themselves, so they
|
|
203
|
+
# play no part in the disable/enable pairing.
|
|
204
|
+
def self_closing_directive?(directive)
|
|
205
|
+
directive.push? || directive.pop? || directive.disable_next? ||
|
|
206
|
+
directive.next? || directive.enable_next?
|
|
207
|
+
end
|
|
208
|
+
|
|
239
209
|
def qualified_cop_name(cop_name)
|
|
240
210
|
# A directive naming a cop under the wrong department is not honored -
|
|
241
211
|
# the mistake is surfaced by `Lint/RedundantCopDisableDirective` (with a
|
data/lib/rubocop/config.rb
CHANGED
|
@@ -226,6 +226,16 @@ module RuboCop
|
|
|
226
226
|
end
|
|
227
227
|
end
|
|
228
228
|
|
|
229
|
+
# Whether preview behavior is on, for cops that gate an unstable change
|
|
230
|
+
# behind it and for cops that are themselves `Enabled: preview`.
|
|
231
|
+
# `--preview` / `--no-preview` win over `AllCops: Preview`.
|
|
232
|
+
def preview?(options = {})
|
|
233
|
+
preview = options[:preview]
|
|
234
|
+
return preview unless preview.nil?
|
|
235
|
+
|
|
236
|
+
for_all_cops['Preview'] == true
|
|
237
|
+
end
|
|
238
|
+
|
|
229
239
|
def active_support_extensions_enabled?
|
|
230
240
|
for_all_cops['ActiveSupportExtensionsEnabled']
|
|
231
241
|
end
|
|
@@ -24,7 +24,8 @@ module RuboCop
|
|
|
24
24
|
include FileFinder
|
|
25
25
|
|
|
26
26
|
attr_accessor :debug, :ignore_parent_exclusion, :disable_pending_cops, :enable_pending_cops,
|
|
27
|
-
:enabled_by_default, :disabled_by_default, :ignore_unrecognized_cops
|
|
27
|
+
:enabled_by_default, :disabled_by_default, :ignore_unrecognized_cops,
|
|
28
|
+
:preview
|
|
28
29
|
attr_writer :default_configuration, :cache_root
|
|
29
30
|
attr_reader :loaded_plugins, :loaded_features
|
|
30
31
|
|
|
@@ -41,6 +42,7 @@ module RuboCop
|
|
|
41
42
|
@loaded_features = Set.new
|
|
42
43
|
@disable_pending_cops = nil
|
|
43
44
|
@enable_pending_cops = nil
|
|
45
|
+
@preview = nil
|
|
44
46
|
@enabled_by_default = nil
|
|
45
47
|
@disabled_by_default = nil
|
|
46
48
|
@ignore_parent_exclusion = nil
|
|
@@ -195,12 +197,18 @@ module RuboCop
|
|
|
195
197
|
# `AllCops/DisabledByDefault` to the given configuration. Used when the
|
|
196
198
|
# configuration would otherwise be returned without going through
|
|
197
199
|
# `merge_with_default` (e.g. there is no user-supplied `.rubocop.yml`).
|
|
200
|
+
# Used when there is no configuration file, so the defaults are the whole
|
|
201
|
+
# configuration and still need the same treatment `merge_with_default`
|
|
202
|
+
# would have given them.
|
|
198
203
|
def apply_default_overrides(config)
|
|
199
|
-
|
|
204
|
+
hash = resolver.apply_preview_defaults(config, preview == true)
|
|
200
205
|
|
|
201
|
-
|
|
202
|
-
|
|
206
|
+
unless @enabled_by_default.nil? && @disabled_by_default.nil?
|
|
207
|
+
hash = hash.transform_values do |params|
|
|
208
|
+
params.is_a?(Hash) ? params.merge('Enabled' => !@disabled_by_default) : params
|
|
209
|
+
end
|
|
203
210
|
end
|
|
211
|
+
|
|
204
212
|
Config.new(hash, config.loaded_path)
|
|
205
213
|
end
|
|
206
214
|
|
|
@@ -95,19 +95,22 @@ module RuboCop
|
|
|
95
95
|
# When the `--disable-all-cops` or `--enable-all-cops` CLI option is given,
|
|
96
96
|
# it takes precedence over the configuration values.
|
|
97
97
|
def merge_with_default(config, config_file, unset_nil:)
|
|
98
|
-
|
|
98
|
+
base_defaults = apply_preview_defaults(ConfigLoader.default_configuration, preview?(config))
|
|
99
|
+
default_configuration = base_defaults
|
|
99
100
|
disabled_by_default, enabled_by_default = resolve_default_overrides(config)
|
|
100
101
|
|
|
101
102
|
if disabled_by_default || enabled_by_default
|
|
102
|
-
default_configuration = transform(
|
|
103
|
+
default_configuration = transform(base_defaults) do |params|
|
|
103
104
|
params.merge('Enabled' => !disabled_by_default)
|
|
104
105
|
end
|
|
105
106
|
end
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
if disabled_by_default
|
|
109
|
+
config = handle_disabled_by_default(config, default_configuration, base_defaults)
|
|
110
|
+
end
|
|
108
111
|
override_enabled_for_disabled_departments(default_configuration, config)
|
|
109
112
|
|
|
110
|
-
opts = { inherit_mode: config
|
|
113
|
+
opts = { inherit_mode: inherit_mode_for_default(config), unset_nil: unset_nil }
|
|
111
114
|
Config.new(merge(default_configuration, config, **opts), config_file)
|
|
112
115
|
end
|
|
113
116
|
|
|
@@ -166,8 +169,47 @@ module RuboCop
|
|
|
166
169
|
end
|
|
167
170
|
end
|
|
168
171
|
|
|
172
|
+
# A cop's entry in the default configuration may carry a `Preview` section
|
|
173
|
+
# with the defaults it is expected to adopt in the next major release. Under
|
|
174
|
+
# `Preview` those replace the current defaults. The section is dropped either
|
|
175
|
+
# way, so the resolved configuration only ever shows what is in effect.
|
|
176
|
+
def apply_preview_defaults(default_configuration, preview)
|
|
177
|
+
transform(default_configuration) do |params|
|
|
178
|
+
next params unless params['Preview'].is_a?(Hash)
|
|
179
|
+
|
|
180
|
+
params = params.dup
|
|
181
|
+
preview_params = params.delete('Preview')
|
|
182
|
+
preview ? params.merge(preview_params) : params
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
169
186
|
private
|
|
170
187
|
|
|
188
|
+
def inherit_mode_for_default(config)
|
|
189
|
+
with_preview_exclude_merge(config['inherit_mode'] || {}, config)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Under `Preview`, `Exclude` is merged rather than replaced, so that excluding
|
|
193
|
+
# one directory does not silently drop the excludes it would have inherited -
|
|
194
|
+
# from the default configuration or from a file named in `inherit_from`. An
|
|
195
|
+
# explicit `inherit_mode` still wins, in either direction.
|
|
196
|
+
def with_preview_exclude_merge(mode, config)
|
|
197
|
+
return mode unless preview?(config)
|
|
198
|
+
return mode if Array(mode['override']).include?('Exclude')
|
|
199
|
+
return mode if Array(mode['merge']).include?('Exclude')
|
|
200
|
+
|
|
201
|
+
mode.merge('merge' => Array(mode['merge']) + ['Exclude'])
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# `config` has already been through `handle_disabled_by_default` by this
|
|
205
|
+
# point, which returns a plain hash, so read `AllCops` directly rather than
|
|
206
|
+
# going through `Config#for_all_cops`.
|
|
207
|
+
def preview?(config)
|
|
208
|
+
return ConfigLoader.preview unless ConfigLoader.preview.nil?
|
|
209
|
+
|
|
210
|
+
(config['AllCops'] || {})['Preview'] == true
|
|
211
|
+
end
|
|
212
|
+
|
|
171
213
|
def resolve_default_overrides(config)
|
|
172
214
|
if ConfigLoader.disabled_by_default || ConfigLoader.enabled_by_default
|
|
173
215
|
[ConfigLoader.disabled_by_default, ConfigLoader.enabled_by_default]
|
|
@@ -210,7 +252,7 @@ module RuboCop
|
|
|
210
252
|
def determine_inherit_mode(hash, key)
|
|
211
253
|
cop_cfg = hash[key]
|
|
212
254
|
local_inherit = cop_cfg['inherit_mode'] if cop_cfg.is_a?(Hash)
|
|
213
|
-
local_inherit || hash['inherit_mode'] || {}
|
|
255
|
+
with_preview_exclude_merge(local_inherit || hash['inherit_mode'] || {}, hash)
|
|
214
256
|
end
|
|
215
257
|
|
|
216
258
|
def should_union?(derived_hash, base_hash, root_mode, key)
|
|
@@ -276,7 +318,7 @@ module RuboCop
|
|
|
276
318
|
file.is_a?(RemoteConfig)
|
|
277
319
|
end
|
|
278
320
|
|
|
279
|
-
def handle_disabled_by_default(config, new_default_configuration)
|
|
321
|
+
def handle_disabled_by_default(config, new_default_configuration, base_defaults)
|
|
280
322
|
department_config = config.to_hash.reject { |cop| cop.include?('/') }
|
|
281
323
|
department_config.each do |dept, dept_params|
|
|
282
324
|
next unless dept_params['Enabled']
|
|
@@ -285,7 +327,7 @@ module RuboCop
|
|
|
285
327
|
next unless cop.start_with?("#{dept}/")
|
|
286
328
|
|
|
287
329
|
# Retain original default configuration for cops in the department.
|
|
288
|
-
params['Enabled'] =
|
|
330
|
+
params['Enabled'] = base_defaults[cop]['Enabled']
|
|
289
331
|
end
|
|
290
332
|
end
|
|
291
333
|
|
|
@@ -20,7 +20,8 @@ module RuboCop
|
|
|
20
20
|
rule_message + "\n(obsolete configuration found in #{smart_loaded_path}, please update it)"
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
#
|
|
23
|
+
# Failure by default; rules that accept `severity: warning` say so
|
|
24
|
+
# themselves.
|
|
24
25
|
def warning?
|
|
25
26
|
false
|
|
26
27
|
end
|
|
@@ -15,7 +15,7 @@ module RuboCop
|
|
|
15
15
|
'changed_parameters' => ChangedParameter,
|
|
16
16
|
'changed_enforced_styles' => ChangedEnforcedStyles
|
|
17
17
|
}.freeze
|
|
18
|
-
LOAD_RULES_CACHE = {} # rubocop:disable Style/MutableConstant
|
|
18
|
+
LOAD_RULES_CACHE = {} # rubocop:disable Style/MutableConstant -- a cache, written to at runtime
|
|
19
19
|
private_constant :LOAD_RULES_CACHE
|
|
20
20
|
|
|
21
21
|
attr_reader :rules, :warnings
|