rubocop-schema-gen 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/.rubocop.yml +37 -1
- data/.ruby-version +1 -1
- data/.travis.yml +5 -1
- data/Gemfile +6 -1
- data/LICENSE +13 -0
- data/README.md +20 -25
- data/assets/templates/cop_schema.yml +1 -3
- data/assets/templates/schema.yml +4 -0
- data/bin/console +1 -0
- data/exe/rubocop-schema-gen +4 -2
- data/lib/rubocop/schema.rb +0 -3
- data/lib/rubocop/schema/ascii_doc/base.rb +53 -0
- data/lib/rubocop/schema/ascii_doc/cop.rb +93 -0
- data/lib/rubocop/schema/ascii_doc/department.rb +21 -0
- data/lib/rubocop/schema/ascii_doc/index.rb +20 -0
- data/lib/rubocop/schema/ascii_doc/stringifier.rb +49 -0
- data/lib/rubocop/schema/{cache.rb → cached_http_client.rb} +7 -11
- data/lib/rubocop/schema/cli.rb +55 -25
- data/lib/rubocop/schema/cop_info_merger.rb +54 -0
- data/lib/rubocop/schema/cop_schema.rb +69 -26
- data/lib/rubocop/schema/defaults_ripper.rb +46 -0
- data/lib/rubocop/schema/document_loader.rb +41 -0
- data/lib/rubocop/schema/extension_spec.rb +59 -0
- data/lib/rubocop/schema/generator.rb +93 -0
- data/lib/rubocop/schema/helpers.rb +51 -0
- data/lib/rubocop/schema/value_objects.rb +23 -3
- data/lib/rubocop/schema/version.rb +1 -1
- data/rubocop-schema.gemspec +4 -3
- data/rubocop-schema.json +4174 -3086
- metadata +34 -11
- data/lib/rubocop/schema/lockfile_inspector.rb +0 -51
- data/lib/rubocop/schema/scraper.rb +0 -183
- data/lib/rubocop/schema/templates.rb +0 -8
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-schema-gen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil E. Pearson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-04-
|
11
|
+
date: 2021-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: asciidoctor
|
@@ -24,21 +24,35 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 2.0.14
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.17'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.17'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: nokogiri
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '1
|
47
|
+
version: '1'
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '1
|
41
|
-
description: Generate JSON schemas for IDE integration with
|
54
|
+
version: '1'
|
55
|
+
description: Generate JSON schemas for IDE integration with RuboCop
|
42
56
|
email:
|
43
57
|
- neil@helium.net.au
|
44
58
|
executables:
|
@@ -53,6 +67,7 @@ files:
|
|
53
67
|
- ".travis.yml"
|
54
68
|
- CODE_OF_CONDUCT.md
|
55
69
|
- Gemfile
|
70
|
+
- LICENSE
|
56
71
|
- LICENSE.txt
|
57
72
|
- README.md
|
58
73
|
- Rakefile
|
@@ -62,12 +77,20 @@ files:
|
|
62
77
|
- bin/setup
|
63
78
|
- exe/rubocop-schema-gen
|
64
79
|
- lib/rubocop/schema.rb
|
65
|
-
- lib/rubocop/schema/
|
80
|
+
- lib/rubocop/schema/ascii_doc/base.rb
|
81
|
+
- lib/rubocop/schema/ascii_doc/cop.rb
|
82
|
+
- lib/rubocop/schema/ascii_doc/department.rb
|
83
|
+
- lib/rubocop/schema/ascii_doc/index.rb
|
84
|
+
- lib/rubocop/schema/ascii_doc/stringifier.rb
|
85
|
+
- lib/rubocop/schema/cached_http_client.rb
|
66
86
|
- lib/rubocop/schema/cli.rb
|
87
|
+
- lib/rubocop/schema/cop_info_merger.rb
|
67
88
|
- lib/rubocop/schema/cop_schema.rb
|
68
|
-
- lib/rubocop/schema/
|
69
|
-
- lib/rubocop/schema/
|
70
|
-
- lib/rubocop/schema/
|
89
|
+
- lib/rubocop/schema/defaults_ripper.rb
|
90
|
+
- lib/rubocop/schema/document_loader.rb
|
91
|
+
- lib/rubocop/schema/extension_spec.rb
|
92
|
+
- lib/rubocop/schema/generator.rb
|
93
|
+
- lib/rubocop/schema/helpers.rb
|
71
94
|
- lib/rubocop/schema/value_objects.rb
|
72
95
|
- lib/rubocop/schema/version.rb
|
73
96
|
- rubocop-schema.gemspec
|
@@ -87,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
110
|
requirements:
|
88
111
|
- - ">="
|
89
112
|
- !ruby/object:Gem::Version
|
90
|
-
version:
|
113
|
+
version: 2.4.0
|
91
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
115
|
requirements:
|
93
116
|
- - ">="
|
@@ -97,5 +120,5 @@ requirements: []
|
|
97
120
|
rubygems_version: 3.2.3
|
98
121
|
signing_key:
|
99
122
|
specification_version: 4
|
100
|
-
summary: Generate JSON schemas for IDE integration with
|
123
|
+
summary: Generate JSON schemas for IDE integration with RuboCop
|
101
124
|
test_files: []
|
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'bundler'
|
2
|
-
require 'pathname'
|
3
|
-
|
4
|
-
module RuboCop
|
5
|
-
module Schema
|
6
|
-
class LockfileInspector
|
7
|
-
Spec = Struct.new(:name, :version, keyword_init: true) do
|
8
|
-
def short_name
|
9
|
-
return nil if name == 'rubocop'
|
10
|
-
|
11
|
-
name[8..]
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
KNOWN_GEMS = Set.new(
|
16
|
-
%w[
|
17
|
-
rubocop
|
18
|
-
rubocop-performance
|
19
|
-
rubocop-rails
|
20
|
-
rubocop-rspec
|
21
|
-
rubocop-minitest
|
22
|
-
rubocop-rake
|
23
|
-
rubocop-sequel
|
24
|
-
]
|
25
|
-
)
|
26
|
-
|
27
|
-
# @return [Pathname]
|
28
|
-
attr_reader :lockfile_path
|
29
|
-
|
30
|
-
def initialize(lockfile_path)
|
31
|
-
@lockfile_path = Pathname(lockfile_path)
|
32
|
-
end
|
33
|
-
|
34
|
-
# @return [Array<Spec>]
|
35
|
-
def specs
|
36
|
-
return [] unless @lockfile_path.readable?
|
37
|
-
|
38
|
-
@specs ||= Bundler::LockfileParser.new(@lockfile_path.to_s).sources.flat_map do |source|
|
39
|
-
source.specs.map do |stub|
|
40
|
-
next unless KNOWN_GEMS.include? stub.name
|
41
|
-
|
42
|
-
Spec.new(
|
43
|
-
name: stub.name,
|
44
|
-
version: stub.version.to_s
|
45
|
-
)
|
46
|
-
end.compact
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
@@ -1,183 +0,0 @@
|
|
1
|
-
require 'asciidoctor'
|
2
|
-
require 'nokogiri'
|
3
|
-
|
4
|
-
require 'rubocop/schema/cache'
|
5
|
-
require 'rubocop/schema/lockfile_inspector'
|
6
|
-
require 'rubocop/schema/templates'
|
7
|
-
require 'rubocop/schema/value_objects'
|
8
|
-
|
9
|
-
module RuboCop
|
10
|
-
module Schema
|
11
|
-
class Scraper
|
12
|
-
DEFAULT_VERSION = -'master'
|
13
|
-
URL_TEMPLATE =
|
14
|
-
-'https://raw.githubusercontent.com/rubocop/rubocop%s/%s/docs/modules/ROOT/pages/cops%s.adoc'
|
15
|
-
|
16
|
-
# @param [LockfileInspector] lockfile
|
17
|
-
# @param [Object] cache
|
18
|
-
def initialize(lockfile, cache)
|
19
|
-
raise ArgumentError unless cache.is_a? Cache
|
20
|
-
raise ArgumentError unless lockfile.is_a? LockfileInspector
|
21
|
-
|
22
|
-
@cache = cache
|
23
|
-
@lockfile = lockfile
|
24
|
-
end
|
25
|
-
|
26
|
-
def schema
|
27
|
-
Schema.template('schema').tap do |json|
|
28
|
-
properties = json.fetch('properties')
|
29
|
-
|
30
|
-
lockfile.specs.each do |spec|
|
31
|
-
index(spec).each do |department_name|
|
32
|
-
dept_info = CopInfo.new(
|
33
|
-
name: department_name,
|
34
|
-
description: "Department #{department_name}"
|
35
|
-
)
|
36
|
-
dept_info.description << " (#{spec.short_name} extension)" if spec.short_name
|
37
|
-
properties[department_name] = cop_schema(dept_info)
|
38
|
-
|
39
|
-
info_for(spec, department_name).each do |cop_info|
|
40
|
-
properties[cop_info.name] = cop_schema(cop_info)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
# @return [LockfileInspector]
|
50
|
-
attr_reader :lockfile
|
51
|
-
|
52
|
-
# @return [Cache]
|
53
|
-
attr_reader :cache
|
54
|
-
|
55
|
-
def info_for(spec, department)
|
56
|
-
doc = load_doc(extension: spec.short_name, version: spec.version, department: department)
|
57
|
-
cop_blocks = doc.query(context: :section) { |s| s.title.start_with? "#{department}/" }
|
58
|
-
cop_blocks.map do |section|
|
59
|
-
info = CopInfo.new(name: section.title)
|
60
|
-
|
61
|
-
description = []
|
62
|
-
# Stats table
|
63
|
-
stats_table_block =
|
64
|
-
section
|
65
|
-
.query(context: :table) { |t| t.rows.head.first.first.text == 'Enabled by default' }
|
66
|
-
.first
|
67
|
-
|
68
|
-
if stats_table_block
|
69
|
-
stats_table = table_to_hash(stats_table_block).first
|
70
|
-
description << "Default: #{stats_table['Enabled by default']}"
|
71
|
-
info.supports_autocorrect = stats_table['Supports autocorrection'] == 'Yes'
|
72
|
-
end
|
73
|
-
|
74
|
-
# Description
|
75
|
-
top = section.blocks.index(stats_table_block) || -1
|
76
|
-
top += 1
|
77
|
-
bottom = section.blocks.index(section.sections.first) || 0
|
78
|
-
bottom -= 1
|
79
|
-
description = section.blocks[top..bottom].map do |s|
|
80
|
-
case s.context
|
81
|
-
when :paragraph, :admonition, :listing
|
82
|
-
s.lines.join(' ')
|
83
|
-
when :literal
|
84
|
-
s.lines.map { |l| " #{l}" }.join("\n")
|
85
|
-
when :ulist
|
86
|
-
s.blocks.map { |b| " - #{reverse_html b.text}" }
|
87
|
-
when :olist
|
88
|
-
s.blocks.map.with_index { |b, i| " #{i + 1}. #{reverse_html b.text}" }
|
89
|
-
when :dlist
|
90
|
-
reverse_html s.convert # Too hard, just go HTML for now
|
91
|
-
else
|
92
|
-
raise "Don't know what to do with #{s.context}"
|
93
|
-
end
|
94
|
-
end + description
|
95
|
-
|
96
|
-
info.description = description.join("\n\n") unless description.empty?
|
97
|
-
|
98
|
-
# Configurable attributes
|
99
|
-
|
100
|
-
attr_table_block = section
|
101
|
-
.query(context: :section) { |s| s.title == 'Configurable attributes' }&.first
|
102
|
-
&.query(context: :table)&.first
|
103
|
-
|
104
|
-
if attr_table_block
|
105
|
-
info.attributes = table_to_hash(attr_table_block).map do |row|
|
106
|
-
type = row['Configurable values']
|
107
|
-
type = type.scan(/\w+/) if type.start_with? '`'
|
108
|
-
Attribute.new(
|
109
|
-
name: row['Name'],
|
110
|
-
default: row['Default value'],
|
111
|
-
type: type
|
112
|
-
)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
info
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
# @param [LockFileInspector::Spec] spec
|
121
|
-
def index(spec)
|
122
|
-
doc = load_doc(extension: spec.short_name, version: spec.version)
|
123
|
-
dept_blocks = doc.query(context: :section) { |s| s.title.start_with? 'Department ' }
|
124
|
-
dept_blocks.map { |section| link_text section.title }
|
125
|
-
end
|
126
|
-
|
127
|
-
def load_doc(...)
|
128
|
-
# noinspection RubyResolve
|
129
|
-
Asciidoctor.load cache.get url_for(...)
|
130
|
-
end
|
131
|
-
|
132
|
-
def url_for(department: nil, version: DEFAULT_VERSION, extension: nil)
|
133
|
-
version = "v#{version}" if version =~ /\A\d+\./
|
134
|
-
format(URL_TEMPLATE, extension && "-#{extension}", version, department && "_#{department.to_s.downcase}")
|
135
|
-
end
|
136
|
-
|
137
|
-
def link_text(str)
|
138
|
-
# The Asciidoctor API doesn't provide access to the raw title, or parts of it.
|
139
|
-
# If performance becomes an issue, this could become a regexp or similarly crude solution.
|
140
|
-
Nokogiri::HTML(str).at_css('a')&.text
|
141
|
-
end
|
142
|
-
|
143
|
-
def reverse_html(str)
|
144
|
-
str = str.gsub(%r{</?code>}, '`')
|
145
|
-
Nokogiri::HTML(str).text
|
146
|
-
end
|
147
|
-
|
148
|
-
# @param [Asciidoctor::Table] table
|
149
|
-
def table_to_hash(table)
|
150
|
-
headings = table.rows.head.first.map(&:text)
|
151
|
-
table.rows.body.map do |row|
|
152
|
-
headings.each_with_index.to_h do |heading, i|
|
153
|
-
[heading, reverse_html(row[i].text)]
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
KNOWN_TYPES = Set.new(%w[boolean integer array string]).freeze
|
159
|
-
|
160
|
-
# @param [CopInfo] info
|
161
|
-
def cop_schema(info)
|
162
|
-
Schema.template('cop_schema').tap do |json|
|
163
|
-
json['description'] = info.description
|
164
|
-
json['properties'] = props = json.fetch('properties').dup
|
165
|
-
|
166
|
-
props['AutoCorrect'] = { 'type' => 'boolean' } if info.supports_autocorrect
|
167
|
-
|
168
|
-
info.attributes&.each do |attr|
|
169
|
-
props[attr.name] = prop = {}
|
170
|
-
prop['description'] = "Default: #{attr.default}" unless attr.default.blank?
|
171
|
-
case attr.type
|
172
|
-
when Array
|
173
|
-
prop['enum'] = attr.type
|
174
|
-
when String
|
175
|
-
type = attr.type.downcase
|
176
|
-
prop['type'] = type if KNOWN_TYPES.include? type
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|