rubocop-faker 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.
- checksums.yaml +7 -0
- data/.circleci/config.yml +43 -0
- data/.gitignore +61 -0
- data/.rspec +3 -0
- data/.rspec_status +4 -0
- data/.rubocop.yml +81 -0
- data/.rubocop_todo.yml +23 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +63 -0
- data/Rakefile +66 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/default.yml +450 -0
- data/lib/rubocop-faker.rb +11 -0
- data/lib/rubocop/cop/faker/deprecated_arguments.rb +113 -0
- data/lib/rubocop/cop/faker_cops.rb +3 -0
- data/lib/rubocop/faker.rb +12 -0
- data/lib/rubocop/faker/inject.rb +18 -0
- data/lib/rubocop/faker/version.rb +7 -0
- data/manual/cops.md +6 -0
- data/manual/cops_faker.md +30 -0
- data/rubocop-faker.gemspec +29 -0
- data/tasks/cops_documentation.rake +310 -0
- metadata +83 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Faker
|
6
|
+
#
|
7
|
+
# Checks that Faker arguments style is based on Faker 2.
|
8
|
+
# Use keyword arguments instead of positional arguments.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# Avatar.image(slug, size, format)
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# Avatar.image(slug: slug, size: size, format: format)
|
16
|
+
#
|
17
|
+
class DeprecatedArguments < Cop
|
18
|
+
include RangeHelp
|
19
|
+
|
20
|
+
MSG = 'Passing `%<arg>s` with the %<index>s argument of ' \
|
21
|
+
'`%<class_name>s.%<method_name>s` is deprecated. ' \
|
22
|
+
'Use keyword argument like `%<class_name>s.%<method_name>s' \
|
23
|
+
'(%<keyword>s: %<arg>s)` instead.'
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
return unless node.receiver
|
27
|
+
|
28
|
+
class_name = faker_class_name(node)
|
29
|
+
|
30
|
+
return unless (methods = argument_keywords[class_name])
|
31
|
+
return unless (keywords = methods[node.method_name.to_s])
|
32
|
+
|
33
|
+
node.arguments.each_with_index do |argument, index|
|
34
|
+
next if argument.hash_type?
|
35
|
+
|
36
|
+
message = format_message(
|
37
|
+
keyword: keywords[index], arg: argument.source,
|
38
|
+
class_name: class_name, index: index,
|
39
|
+
method_name: node.method_name
|
40
|
+
)
|
41
|
+
|
42
|
+
add_offense_for_arguments(node, argument, message)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def autocorrect(node)
|
47
|
+
methods = argument_keywords[faker_class_name(node)]
|
48
|
+
keywords = methods[node.method_name.to_s]
|
49
|
+
|
50
|
+
kwargs = build_kwargs_style(node, keywords)
|
51
|
+
|
52
|
+
lambda do |corrector|
|
53
|
+
corrector.replace(arguments_range(node), kwargs)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def format_message(keyword:, arg:, index:, class_name:, method_name:)
|
60
|
+
i = case index
|
61
|
+
when 0 then '1st'
|
62
|
+
when 1 then '2nd'
|
63
|
+
when 2 then '3rd'
|
64
|
+
else "#{index + 1}th"
|
65
|
+
end
|
66
|
+
format(
|
67
|
+
MSG,
|
68
|
+
keyword: keyword,
|
69
|
+
arg: arg,
|
70
|
+
index: i,
|
71
|
+
class_name: class_name,
|
72
|
+
method_name: method_name
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_offense_for_arguments(node, argument, message)
|
77
|
+
add_offense(
|
78
|
+
node,
|
79
|
+
location: argument.source_range,
|
80
|
+
message: message
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_kwargs_style(node, keywords)
|
85
|
+
node.arguments.map.with_index do |positional_argument, index|
|
86
|
+
if positional_argument.hash_type?
|
87
|
+
positional_argument.source
|
88
|
+
else
|
89
|
+
"#{keywords[index]}: #{positional_argument.source}"
|
90
|
+
end
|
91
|
+
end.join(', ')
|
92
|
+
end
|
93
|
+
|
94
|
+
def faker_class_name(node)
|
95
|
+
node.receiver.source
|
96
|
+
end
|
97
|
+
|
98
|
+
def arguments_range(node)
|
99
|
+
arguments = node.arguments
|
100
|
+
|
101
|
+
range_between(
|
102
|
+
arguments.first.source_range.begin_pos,
|
103
|
+
arguments.last.source_range.end_pos
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def argument_keywords
|
108
|
+
cop_config.fetch('ArgumentKeywords', {})
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
# RuboCop Faker project namespace
|
5
|
+
module Faker
|
6
|
+
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
|
7
|
+
CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
|
8
|
+
CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
|
9
|
+
|
10
|
+
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Faker
|
5
|
+
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
6
|
+
# bit of our configuration.
|
7
|
+
module Inject
|
8
|
+
def self.defaults!
|
9
|
+
path = CONFIG_DEFAULT.to_s
|
10
|
+
hash = ConfigLoader.send(:load_yaml_configuration, path)
|
11
|
+
config = Config.new(hash, path)
|
12
|
+
puts "configuration from #{path}" if ConfigLoader.debug?
|
13
|
+
config = ConfigLoader.merge_with_default(config, path)
|
14
|
+
ConfigLoader.instance_variable_set(:@default_configuration, config)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/manual/cops.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Faker
|
2
|
+
|
3
|
+
## Faker/DeprecatedArguments
|
4
|
+
|
5
|
+
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
6
|
+
--- | --- | --- | --- | ---
|
7
|
+
Enabled | Yes | Yes | 0.1 | -
|
8
|
+
|
9
|
+
Checks that Faker arguments style is based on Faker 2.
|
10
|
+
Use keyword arguments instead of positional arguments.
|
11
|
+
|
12
|
+
### Examples
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
# bad
|
16
|
+
Avatar.image(slug, size, format)
|
17
|
+
|
18
|
+
# good
|
19
|
+
Avatar.image(slug: slug, size: size, format: format)
|
20
|
+
```
|
21
|
+
|
22
|
+
### Configurable attributes
|
23
|
+
|
24
|
+
Name | Default value | Configurable values
|
25
|
+
--- | --- | ---
|
26
|
+
ArgumentKeywords | `{"Faker::Dune"=>{"quote"=>["character"], "saying"=>["source"]}, "Faker::Books::Lovecraft"=>{"fhtagn"=>["number"], "sentence"=>["word_count", "random_words_to_add"], "words"=>["number", "spaces_allowed"], "sentences"=>["number"], "paragraph"=>["sentence_count", "random_sentences_to_add"], "paragraphs"=>["number"], "paragraph_by_chars"=>["characters"]}, "Faker::Address"=>{"city"=>["options"], "street_address"=>["include_secondary"], "zip_code"=>["state_abbreviation"], "country_by_code"=>["code"], "country_name_to_code"=>["name"]}, "Faker::Alphanumeric"=>{"alpha"=>["number"], "alphanumeric"=>["number"]}, "Faker::App"=>{"semantic_version"=>["major", "minor", "patch"]}, "Faker::Avatar"=>{"image"=>["slug", "size", "format", "set", "bgset"]}, "Faker::Bank"=>{"account_number"=>["digits"], "iban"=>["country_code"]}, "Faker::Boolean"=>{"boolean"=>["true_ratio"]}, "Faker::ChileRut"=>{"rut"=>["min_rut", "fixed"], "full_rut"=>["min_rut", "fixed"]}, "Faker::Code"=>{"isbn"=>["base"], "ean"=>["base"], "nric"=>["min_age", "max_age"]}, "Faker::Commerce"=>{"promotion_code"=>["digits"], "department"=>["max", "fixed_amount"], "price"=>["range", "as_string"]}, "Faker::Company"=>{"polish_register_of_national_economy"=>["length"], "brazilian_company_number"=>["formatted"]}, "Faker::CryptoCoin"=>{"coin_name"=>["coin"], "acronym"=>["coin"], "url_logo"=>["coin"]}, "Faker::Date"=>{"between"=>["from", "to"], "between_except"=>["from", "to", "excepted"], "forward"=>["days"], "backward"=>["days"], "birthday"=>["min_age", "max_age"]}, "Faker::Demographic"=>{"height"=>["unit"]}, "Faker::DrivingLicence"=>{"british_driving_licence"=>["last_name", "initials", "gender", "date_of_birth"]}, "Faker::File"=>{"dir"=>["segment_count", "root", "directory_separator"], "file_name"=>["dir", "name", "ext", "directory_separator"]}, "Faker::Fillmurray"=>{"image"=>["grayscale", "width", "height"]}, "Faker::Finance"=>{"vat_number"=>["country"]}, "Faker::Hipster"=>{"words"=>["number", "supplemental", "spaces_allowed"], "sentence"=>["word_count", "supplemental", "random_words_to_add"], "sentences"=>["number", "supplemental"], "paragraph"=>["sentence_count", "supplemental", "random_sentences_to_add"], "paragraphs"=>["number", "supplemental"], "paragraph_by_chars"=>["characters", "supplemental"]}, "Faker::IDNumber"=>{"brazilian_citizen_number"=>["formatted"], "brazilian_id"=>["formatted"]}, "Faker::Internet"=>{"email"=>["name", "separators"], "free_email"=>["name"], "safe_email"=>["name"], "username"=>["specifier", "separators"], "password"=>["min_length", "max_length", "mix_case", "special_characters"], "domain_name"=>["subdomain"], "fix_umlauts"=>["string"], "mac_address"=>["prefix"], "url"=>["host", "path", "scheme"], "slug"=>["words", "glue"], "user_agent"=>["vendor"]}, "Faker::Invoice"=>{"amount_between"=>["from", "to"], "creditor_reference"=>["ref"], "reference"=>["ref"]}, "Faker::Json"=>{"shallow_json"=>["width", "options"], "add_depth_to_json"=>["json", "width", "options"]}, "Faker::Lorem"=>{"words"=>["number", "supplemental"], "characters"=>["number"], "sentence"=>["word_count", "supplemental", "random_words_to_add"], "sentences"=>["number", "supplemental"], "paragraph"=>["sentence_count", "supplemental", "random_sentences_to_add"], "paragraphs"=>["number", "supplemental"], "paragraph_by_chars"=>["number", "supplemental"], "question"=>["word_count", "supplemental", "random_words_to_add"], "questions"=>["number", "supplemental"]}, "Faker::LoremFlickr"=>{"image"=>["size", "search_terms", "match_all"], "grayscale_image"=>["size", "search_terms", "match_all"], "pixelated_image"=>["size", "search_terms", "match_all"], "colorized_image"=>["size", "color", "search_terms", "match_all"]}, "Faker::LoremPixel"=>{"image"=>["size", "is_gray", "category", "number", "text", "secure"]}, "Faker::Markdown"=>{"sandwich"=>["sentences", "repeat"]}, "Faker::Measurement"=>{"height"=>["amount"], "length"=>["amount"], "volume"=>["amount"], "weight"=>["amount"], "metric_height"=>["amount"], "metric_length"=>["amount"], "metric_volume"=>["amount"], "metric_weight"=>["amount"]}, "Faker::Name"=>{"initials"=>["number"]}, "Faker::NationalHealthService"=>{"check_digit"=>["number"]}, "Faker::Number"=>{"number"=>["digits"], "leading_zero_number"=>["digits"], "decimal_part"=>["digits"], "decimal"=>["l_digits", "r_digits"], "hexadecimal"=>["digits"], "normal"=>["mean", "standard_deviation"], "between"=>["from", "to"], "within"=>["range"], "positive"=>["from", "to"], "negative"=>["from", "to"]}, "Faker::Omniauth"=>{"google"=>["name", "email", "uid"], "facebook"=>["name", "email", "username", "uid"], "twitter"=>["name", "nickname", "uid"], "linkedin"=>["name", "email", "uid"], "github"=>["name", "email", "uid"]}, "Faker::PhoneNumber"=>{"subscriber_number"=>["length"]}, "Faker::Placeholdit"=>{"image"=>["size", "format", "background_color", "text_color", "text"]}, "Faker::Relationship"=>{"familial"=>["connection"]}, "Faker::Source"=>{"hello_world"=>["lang"], "print"=>["str", "lang"], "print_1_to_10"=>["lang"]}, "Faker::String"=>{"random"=>["length"]}, "Faker::Stripe"=>{"valid_card"=>["card_type"], "valid_token"=>["card_type"], "invalid_card"=>["card_error"], "ccv"=>["card_type"]}, "Faker::Time"=>{"between"=>["from", "to", "format"], "between_dates"=>["from", "to", "period", "format"], "forward"=>["days", "period", "format"], "backward"=>["days", "period", "format"]}, "Faker::Twitter"=>{"user"=>["include_status", "include_email"], "status"=>["include_user", "include_photo"], "status_entities"=>["include_photo"]}, "Faker::Types"=>{"rb_string"=>["words"], "rb_integer"=>["from", "to"], "rb_hash"=>["number", "type"], "complex_rb_hash"=>["number"], "rb_array"=>["len"]}, "Faker::Vehicle"=>{"model"=>["make_of_model"], "mileage"=>["min", "max"], "license_plate"=>["state_abreviation"]}, "Faker::WorldCup"=>{"group"=>["group"], "roster"=>["country", "type"]}, "Faker::Dota"=>{"quote"=>["hero"]}, "Faker::Movies::StarWars"=>{"quote"=>["character"]}}` |
|
27
|
+
|
28
|
+
### References
|
29
|
+
|
30
|
+
* [https://github.com/faker-ruby/faker/blob/master/CHANGELOG.md#v20-2019-31-07](https://github.com/faker-ruby/faker/blob/master/CHANGELOG.md#v20-2019-31-07)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/rubocop/faker/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'rubocop-faker'
|
7
|
+
spec.version = RuboCop::Faker::VERSION
|
8
|
+
spec.authors = ['Koichi ITO']
|
9
|
+
spec.email = ['koic.ito@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'A RuboCop extension for Faker'
|
12
|
+
spec.description = 'A RuboCop extension for Faker'
|
13
|
+
spec.homepage = 'https://github.com/koic/rubocop-faker'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
end
|
24
|
+
spec.bindir = 'exe'
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ['lib']
|
27
|
+
|
28
|
+
spec.add_runtime_dependency 'rubocop', '>= 0.74'
|
29
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yard'
|
4
|
+
require 'rubocop'
|
5
|
+
require 'rubocop-faker'
|
6
|
+
|
7
|
+
YARD::Rake::YardocTask.new(:yard_for_generate_documentation) do |task|
|
8
|
+
task.files = ['lib/rubocop/cop/**/*.rb']
|
9
|
+
task.options = ['--no-output']
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Generate docs of all cops departments'
|
13
|
+
task generate_cops_documentation: :yard_for_generate_documentation do
|
14
|
+
def cops_of_department(cops, department)
|
15
|
+
cops.with_department(department).sort!
|
16
|
+
end
|
17
|
+
|
18
|
+
def cops_body(config, cop, description, examples_objects, pars)
|
19
|
+
content = h2(cop.cop_name)
|
20
|
+
content << properties(config, cop)
|
21
|
+
content << "#{description}\n"
|
22
|
+
content << examples(examples_objects) if examples_objects.count.positive?
|
23
|
+
content << configurations(pars)
|
24
|
+
content << references(config, cop)
|
25
|
+
content
|
26
|
+
end
|
27
|
+
|
28
|
+
def examples(examples_object)
|
29
|
+
examples_object.each_with_object(h3('Examples').dup) do |example, content|
|
30
|
+
content << h4(example.name) unless example.name == ''
|
31
|
+
content << code_example(example)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# rubocop:disable Metrics/MethodLength
|
36
|
+
def properties(config, cop)
|
37
|
+
header = [
|
38
|
+
'Enabled by default', 'Safe', 'Supports autocorrection', 'VersionAdded',
|
39
|
+
'VersionChanged'
|
40
|
+
]
|
41
|
+
config = config.for_cop(cop)
|
42
|
+
safe_auto_correct = config.fetch('SafeAutoCorrect', true)
|
43
|
+
autocorrect = if cop.new.support_autocorrect?
|
44
|
+
"Yes #{'(Unsafe)' unless safe_auto_correct}"
|
45
|
+
else
|
46
|
+
'No'
|
47
|
+
end
|
48
|
+
content = [[
|
49
|
+
config.fetch('Enabled') ? 'Enabled' : 'Disabled',
|
50
|
+
config.fetch('Safe', true) ? 'Yes' : 'No',
|
51
|
+
autocorrect,
|
52
|
+
config.fetch('VersionAdded', '-'),
|
53
|
+
config.fetch('VersionChanged', '-')
|
54
|
+
]]
|
55
|
+
to_table(header, content) + "\n"
|
56
|
+
end
|
57
|
+
# rubocop:enable Metrics/MethodLength
|
58
|
+
|
59
|
+
def h2(title)
|
60
|
+
content = +"\n"
|
61
|
+
content << "## #{title}\n"
|
62
|
+
content << "\n"
|
63
|
+
content
|
64
|
+
end
|
65
|
+
|
66
|
+
def h3(title)
|
67
|
+
content = +"\n"
|
68
|
+
content << "### #{title}\n"
|
69
|
+
content << "\n"
|
70
|
+
content
|
71
|
+
end
|
72
|
+
|
73
|
+
def h4(title)
|
74
|
+
content = +"#### #{title}\n"
|
75
|
+
content << "\n"
|
76
|
+
content
|
77
|
+
end
|
78
|
+
|
79
|
+
def code_example(ruby_code)
|
80
|
+
content = +"```ruby\n"
|
81
|
+
content << ruby_code.text
|
82
|
+
.gsub('@good', '# good').gsub('@bad', '# bad').strip
|
83
|
+
content << "\n```\n"
|
84
|
+
content
|
85
|
+
end
|
86
|
+
|
87
|
+
def configurations(pars)
|
88
|
+
return '' if pars.empty?
|
89
|
+
|
90
|
+
header = ['Name', 'Default value', 'Configurable values']
|
91
|
+
configs = pars.each_key.reject { |key| key.start_with?('Supported') }
|
92
|
+
content = configs.map do |name|
|
93
|
+
configurable = configurable_values(pars, name)
|
94
|
+
default = format_table_value(pars[name])
|
95
|
+
[name, default, configurable]
|
96
|
+
end
|
97
|
+
|
98
|
+
h3('Configurable attributes') + to_table(header, content)
|
99
|
+
end
|
100
|
+
|
101
|
+
# rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
102
|
+
def configurable_values(pars, name)
|
103
|
+
case name
|
104
|
+
when /^Enforced/
|
105
|
+
supported_style_name = RuboCop::Cop::Util.to_supported_styles(name)
|
106
|
+
format_table_value(pars[supported_style_name])
|
107
|
+
when 'IndentationWidth'
|
108
|
+
'Integer'
|
109
|
+
else
|
110
|
+
case pars[name]
|
111
|
+
when String
|
112
|
+
'String'
|
113
|
+
when Integer
|
114
|
+
'Integer'
|
115
|
+
when Float
|
116
|
+
'Float'
|
117
|
+
when true, false
|
118
|
+
'Boolean'
|
119
|
+
when Array
|
120
|
+
'Array'
|
121
|
+
else
|
122
|
+
''
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
# rubocop:enable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
127
|
+
|
128
|
+
def to_table(header, content)
|
129
|
+
table = [
|
130
|
+
header.join(' | '),
|
131
|
+
Array.new(header.size, '---').join(' | ')
|
132
|
+
]
|
133
|
+
table.concat(content.map { |c| c.join(' | ') })
|
134
|
+
table.join("\n") + "\n"
|
135
|
+
end
|
136
|
+
|
137
|
+
def format_table_value(val)
|
138
|
+
value =
|
139
|
+
case val
|
140
|
+
when Array
|
141
|
+
if val.empty?
|
142
|
+
'`[]`'
|
143
|
+
else
|
144
|
+
val.map { |config| format_table_value(config) }.join(', ')
|
145
|
+
end
|
146
|
+
else
|
147
|
+
"`#{val.nil? ? '<none>' : val}`"
|
148
|
+
end
|
149
|
+
value.gsub("#{Dir.pwd}/", '').rstrip
|
150
|
+
end
|
151
|
+
|
152
|
+
def references(config, cop)
|
153
|
+
cop_config = config.for_cop(cop)
|
154
|
+
urls = RuboCop::Cop::MessageAnnotator.new(
|
155
|
+
config, cop.name, cop_config, {}
|
156
|
+
).urls
|
157
|
+
return '' if urls.empty?
|
158
|
+
|
159
|
+
content = h3('References')
|
160
|
+
content << urls.map { |url| "* [#{url}](#{url})" }.join("\n")
|
161
|
+
content << "\n"
|
162
|
+
content
|
163
|
+
end
|
164
|
+
|
165
|
+
def print_cops_of_department(cops, department, config)
|
166
|
+
selected_cops = cops_of_department(cops, department).select do |cop|
|
167
|
+
cop.to_s.start_with?('RuboCop::Cop::Faker')
|
168
|
+
end
|
169
|
+
return if selected_cops.empty?
|
170
|
+
|
171
|
+
content = +"# #{department}\n"
|
172
|
+
selected_cops.each do |cop|
|
173
|
+
content << print_cop_with_doc(cop, config)
|
174
|
+
end
|
175
|
+
file_name = "#{Dir.pwd}/manual/cops_#{department.downcase}.md"
|
176
|
+
File.open(file_name, 'w') do |file|
|
177
|
+
puts "* generated #{file_name}"
|
178
|
+
file.write(content.strip + "\n")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def print_cop_with_doc(cop, config)
|
183
|
+
t = config.for_cop(cop)
|
184
|
+
non_display_keys = %w[
|
185
|
+
Description Enabled StyleGuide Reference Safe SafeAutoCorrect VersionAdded
|
186
|
+
VersionChanged
|
187
|
+
]
|
188
|
+
pars = t.reject { |k| non_display_keys.include? k }
|
189
|
+
description = 'No documentation'
|
190
|
+
examples_object = []
|
191
|
+
YARD::Registry.all(:class).detect do |code_object|
|
192
|
+
next unless RuboCop::Cop::Badge.for(code_object.to_s) == cop.badge
|
193
|
+
|
194
|
+
description = code_object.docstring unless code_object.docstring.blank?
|
195
|
+
examples_object = code_object.tags('example')
|
196
|
+
end
|
197
|
+
cops_body(config, cop, description, examples_object, pars)
|
198
|
+
end
|
199
|
+
|
200
|
+
# rubocop:disable Metrics/AbcSize
|
201
|
+
def table_of_content_for_department(cops, department)
|
202
|
+
selected_cops = cops_of_department(cops, department.to_sym).select do |cop|
|
203
|
+
cop.to_s.start_with?('RuboCop::Cop::Faker')
|
204
|
+
end
|
205
|
+
return if selected_cops.empty?
|
206
|
+
|
207
|
+
type_title = department[0].upcase + department[1..-1]
|
208
|
+
filename = "cops_#{department.downcase}.md"
|
209
|
+
content = +"#### Department [#{type_title}](#{filename})\n\n"
|
210
|
+
selected_cops.each do |cop|
|
211
|
+
anchor = cop.cop_name.sub('/', '').downcase
|
212
|
+
content << "* [#{cop.cop_name}](#{filename}##{anchor})\n"
|
213
|
+
end
|
214
|
+
|
215
|
+
content
|
216
|
+
end
|
217
|
+
# rubocop:enable Metrics/AbcSize
|
218
|
+
|
219
|
+
def print_table_of_contents(cops)
|
220
|
+
path = "#{Dir.pwd}/manual/cops.md"
|
221
|
+
original = File.read(path)
|
222
|
+
content = +"<!-- START_COP_LIST -->\n"
|
223
|
+
|
224
|
+
content << table_contents(cops)
|
225
|
+
|
226
|
+
content << "\n<!-- END_COP_LIST -->"
|
227
|
+
|
228
|
+
content = if original.empty?
|
229
|
+
content
|
230
|
+
else
|
231
|
+
original.sub(
|
232
|
+
/<!-- START_COP_LIST -->.+<!-- END_COP_LIST -->/m, content
|
233
|
+
)
|
234
|
+
end
|
235
|
+
File.write(path, content)
|
236
|
+
end
|
237
|
+
|
238
|
+
def table_contents(cops)
|
239
|
+
cops
|
240
|
+
.departments
|
241
|
+
.map(&:to_s)
|
242
|
+
.sort
|
243
|
+
.map { |department| table_of_content_for_department(cops, department) }
|
244
|
+
.reject(&:nil?)
|
245
|
+
.join("\n")
|
246
|
+
end
|
247
|
+
|
248
|
+
def assert_manual_synchronized
|
249
|
+
# Do not print diff and yield whether exit code was zero
|
250
|
+
sh('git diff --quiet manual') do |outcome, _|
|
251
|
+
return if outcome
|
252
|
+
|
253
|
+
# Output diff before raising error
|
254
|
+
sh('GIT_PAGER=cat git diff manual')
|
255
|
+
|
256
|
+
warn 'The manual directory is out of sync. ' \
|
257
|
+
'Run `rake generate_cops_documentation` and commit the results.'
|
258
|
+
exit!
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def main
|
263
|
+
cops = RuboCop::Cop::Cop.registry
|
264
|
+
config = RuboCop::ConfigLoader.load_file('config/default.yml')
|
265
|
+
|
266
|
+
YARD::Registry.load!
|
267
|
+
cops.departments.sort!.each do |department|
|
268
|
+
print_cops_of_department(cops, department, config)
|
269
|
+
end
|
270
|
+
|
271
|
+
print_table_of_contents(cops)
|
272
|
+
|
273
|
+
assert_manual_synchronized if ENV['CI'] == 'true'
|
274
|
+
ensure
|
275
|
+
RuboCop::ConfigLoader.default_configuration = nil
|
276
|
+
end
|
277
|
+
|
278
|
+
main
|
279
|
+
end
|
280
|
+
|
281
|
+
desc 'Syntax check for the documentation comments'
|
282
|
+
task documentation_syntax_check: :yard_for_generate_documentation do
|
283
|
+
require 'parser/ruby25'
|
284
|
+
|
285
|
+
ok = true
|
286
|
+
YARD::Registry.load!
|
287
|
+
cops = RuboCop::Cop::Cop.registry
|
288
|
+
cops.each do |cop|
|
289
|
+
examples = YARD::Registry.all(:class).find do |code_object|
|
290
|
+
next unless RuboCop::Cop::Badge.for(code_object.to_s) == cop.badge
|
291
|
+
|
292
|
+
break code_object.tags('example')
|
293
|
+
end
|
294
|
+
|
295
|
+
examples.to_a.each do |example|
|
296
|
+
begin
|
297
|
+
buffer = Parser::Source::Buffer.new('<code>', 1)
|
298
|
+
buffer.source = example.text
|
299
|
+
parser = Parser::Ruby25.new(RuboCop::AST::Builder.new)
|
300
|
+
parser.diagnostics.all_errors_are_fatal = true
|
301
|
+
parser.parse(buffer)
|
302
|
+
rescue Parser::SyntaxError => e
|
303
|
+
path = example.object.file
|
304
|
+
puts "#{path}: Syntax Error in an example. #{e}"
|
305
|
+
ok = false
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
abort unless ok
|
310
|
+
end
|