rubocop-sketchup 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -2
  3. data/config/default.yml +12 -2
  4. data/lib/rubocop-sketchup.rb +18 -0
  5. data/lib/rubocop/sketchup/config.rb +31 -0
  6. data/lib/rubocop/sketchup/dc_globals.rb +24 -0
  7. data/lib/rubocop/sketchup/extension_project.rb +34 -34
  8. data/lib/rubocop/sketchup/formatter/extension_review.rb +239 -0
  9. data/lib/rubocop/sketchup/inject.rb +1 -1
  10. data/lib/rubocop/sketchup/namespace.rb +38 -40
  11. data/lib/rubocop/sketchup/namespace_checker.rb +35 -37
  12. data/lib/rubocop/sketchup/no_comment_disable.rb +1 -1
  13. data/lib/rubocop/sketchup/performance/typename.rb +1 -1
  14. data/lib/rubocop/sketchup/requirements/api_namespace.rb +2 -2
  15. data/lib/rubocop/sketchup/requirements/exit.rb +2 -2
  16. data/lib/rubocop/sketchup/requirements/extension_namespace.rb +2 -2
  17. data/lib/rubocop/sketchup/requirements/file_structure.rb +14 -5
  18. data/lib/rubocop/sketchup/requirements/global_constants.rb +1 -1
  19. data/lib/rubocop/sketchup/requirements/global_methods.rb +1 -1
  20. data/lib/rubocop/sketchup/requirements/global_variables.rb +19 -7
  21. data/lib/rubocop/sketchup/requirements/language_handler_globals.rb +2 -0
  22. data/lib/rubocop/sketchup/requirements/load_path.rb +1 -1
  23. data/lib/rubocop/sketchup/requirements/minimal_registration.rb +3 -3
  24. data/lib/rubocop/sketchup/requirements/register_extension.rb +3 -3
  25. data/lib/rubocop/sketchup/requirements/require_tools_ruby_files.rb +74 -0
  26. data/lib/rubocop/sketchup/requirements/ruby_core_namespace.rb +2 -2
  27. data/lib/rubocop/sketchup/requirements/ruby_stdlib_namespace.rb +2 -2
  28. data/lib/rubocop/sketchup/requirements/shipped_extensions_namespace.rb +2 -2
  29. data/lib/rubocop/sketchup/requirements/sketchup_extension.rb +18 -6
  30. data/lib/rubocop/sketchup/suggestions/dc_internals.rb +2 -13
  31. data/lib/rubocop/sketchup/suggestions/file_encoding.rb +1 -1
  32. data/lib/rubocop/sketchup/suggestions/model_entities.rb +1 -0
  33. data/lib/rubocop/sketchup/suggestions/operation_name.rb +1 -1
  34. data/lib/rubocop/sketchup/version.rb +1 -1
  35. data/rubocop-sketchup.gemspec +4 -2
  36. metadata +10 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a682a986a80b22c8a6a1a4882e7ccf730306dc2
4
- data.tar.gz: f883dfc7ba5d0100b447ac77364078a475c2a770
3
+ metadata.gz: 4d7b803688f7c17a0b11ab8b6ac5452d1a4fa925
4
+ data.tar.gz: 3b0c7b2667761ea2d5a298f9906aeb5c0dd90c93
5
5
  SHA512:
6
- metadata.gz: e763bf2331404f463e0d9d46ff19fadd9d642066eaf8b0606e108f089af006670e7fce3d3efb3eaa1abf14870e6bae732e353aa88fdd17632c0fff0f1364ac92
7
- data.tar.gz: e550faed872bb4308a3658e42cb08aa2bc4880787ca6b01aca53fea0bebfa5291411ee6052ed8feb62466a20c00f66de21567f80b3e0736ee4290fa62e487812
6
+ metadata.gz: bb8900616392157c20a242834e96163a94aa73a98179679e518b4882297b9875916431383e90ff2883fe109de663783b78d6d274f6f3be338575c2cf83986396
7
+ data.tar.gz: 0ac20236f7f5e6046dd072ae3722861f3e008709fa9e4e39b5b9f93036124710718c8bfb170df2c8c5a73ecad9f949dc6b7efcbf455214eee1dc4d3138f3c880
data/Gemfile CHANGED
@@ -3,6 +3,10 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :test do
6
- gem 'rake', '~> 10.0', require: false
7
- gem 'rspec', '~> 3.4', require: false
6
+ gem 'rake', '~> 12.0', require: false
7
+ gem 'rspec', '~> 3.7', require: false
8
+ end
9
+
10
+ group :development do
11
+ gem 'nokogiri', '~> 1.8', require: false
8
12
  end
data/config/default.yml CHANGED
@@ -1,4 +1,8 @@
1
1
  ---
2
+ AllCops:
3
+ SketchUp:
4
+ SourcePath: src
5
+
2
6
  SketchupDeprecations/OperationNextTransparent:
3
7
  Description: Third argument in `model.start_operation` is deprecated.
4
8
  Details: >-
@@ -60,7 +64,6 @@ SketchupRequirements/FileStructure:
60
64
  Check that the extension conform to expected file structure with a
61
65
  single root .rb file and a support folder with matching name.
62
66
  Enabled: true
63
- SourcePath: src
64
67
 
65
68
  SketchupRequirements/GlobalConstants:
66
69
  Description: Do not introduce global constants.
@@ -102,6 +105,14 @@ SketchupRequirements/RegisterExtension:
102
105
  might confuse users to think the extension isn't working.
103
106
  Enabled: true
104
107
 
108
+ SketchupRequirements/RequireToolsRubyFiles:
109
+ Description: >-
110
+ Due to how require and Sketchup.require checks whether a file
111
+ has been loaded, files from SketchUp's Tools folder should be
112
+ required in using `require` and with their file extension to
113
+ avoid duplicate loading.
114
+ Enabled: true
115
+
105
116
  SketchupRequirements/RubyCoreNamespace:
106
117
  Description: Do not modify Ruby core functionality.
107
118
  Enabled: true
@@ -120,7 +131,6 @@ SketchupRequirements/SketchupExtension:
120
131
  This should be done by the root .rb file in the extension
121
132
  package.
122
133
  Enabled: true
123
- SourcePath: src
124
134
 
125
135
 
126
136
  SketchupSuggestions/DynamicComponentInternals:
@@ -3,6 +3,9 @@ require 'rubocop/sketchup'
3
3
  require 'rubocop/sketchup/version'
4
4
  require 'rubocop/sketchup/inject'
5
5
 
6
+ require 'rubocop/sketchup/formatter/extension_review'
7
+ require 'rubocop/sketchup/config'
8
+ require 'rubocop/sketchup/dc_globals'
6
9
  require 'rubocop/sketchup/extension_project'
7
10
  require 'rubocop/sketchup/namespace'
8
11
  require 'rubocop/sketchup/namespace_checker'
@@ -10,6 +13,21 @@ require 'rubocop/sketchup/no_comment_disable'
10
13
 
11
14
  RuboCop::SketchUp::Inject.defaults!
12
15
 
16
+ # Monkey patching the built in formatter list to add a short alias for custom
17
+ # formatters. Naughty! Naughty!
18
+ class RuboCop::Formatter::FormatterSet
19
+ formatters = BUILTIN_FORMATTERS_FOR_KEYS.dup
20
+ formatters['extension_review'] =
21
+ RuboCop::Formatter::ExtensionReviewFormatter
22
+ verbose = $VERBOSE
23
+ begin
24
+ $VERBOSE = nil
25
+ BUILTIN_FORMATTERS_FOR_KEYS = formatters.freeze
26
+ ensure
27
+ $VERBOSE = verbose
28
+ end
29
+ end
30
+
13
31
  # Load all custom cops.
14
32
  pattern = File.join(__dir__, 'rubocop', 'sketchup', '**/*rb')
15
33
  Dir.glob(pattern) { |file|
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module SketchUp
5
+ module Config
6
+
7
+ DEFAULT_CONFIGURATION =
8
+ RuboCop::SketchUp::CONFIG.fetch('AllCops').fetch('SketchUp')
9
+
10
+ private
11
+
12
+ def all_cops_config
13
+ config.for_all_cops
14
+ end
15
+
16
+ def sketchup_cops_config
17
+ config.for_all_cops.fetch('SketchUp', DEFAULT_CONFIGURATION)
18
+ end
19
+
20
+ def sketchup_source_path_config?
21
+ return unless all_cops_config.key?('SketchUp')
22
+ all_cops_config.fetch('SketchUp').key?('SourcePath')
23
+ end
24
+
25
+ def sketchup_source_path_config
26
+ sketchup_cops_config.fetch('SourcePath')
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module SketchUp
5
+ module DynamicComponentGlobals
6
+
7
+ DC_GLOBALS = %i[
8
+ $dc_strings
9
+ $dc_extension
10
+ $dc_CONFIGURATOR_NAME
11
+ $dc_REPORTER_NAME
12
+ $dc_MANAGER_NAME
13
+ $dc_observers
14
+ ]
15
+
16
+ private
17
+
18
+ def dc_global_var?(global_var)
19
+ DC_GLOBALS.include?(global_var)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -3,46 +3,46 @@
3
3
  require 'pathname'
4
4
 
5
5
  module RuboCop
6
- module Cop
7
- module Sketchup
8
- module ExtensionProject
9
-
10
- # @return [Pathname]
11
- def config_path
12
- path = config.instance_variable_get(:@loaded_path)
13
- if path
14
- Pathname.new(path).expand_path.dirname
15
- else
16
- Pathname.new(Dir.pwd).expand_path
17
- end
18
- end
19
-
20
- # @return [Pathname]
21
- def relative_source_path
22
- Pathname.new(cop_config['SourcePath'] || 'src')
6
+ module SketchUp
7
+ module ExtensionProject
8
+
9
+ include SketchUp::Config
10
+
11
+ # @return [Pathname]
12
+ def config_path
13
+ path = config.instance_variable_get(:@loaded_path)
14
+ if path
15
+ Pathname.new(path).expand_path.dirname
16
+ else
17
+ Pathname.new(Dir.pwd).expand_path
23
18
  end
19
+ end
24
20
 
25
- # @return [Pathname]
26
- def source_path
27
- config_path.join(relative_source_path)
28
- end
21
+ # @return [Pathname]
22
+ def relative_source_path
23
+ Pathname.new(sketchup_source_path_config)
24
+ end
29
25
 
30
- # @param [RuboCop::ProcessedSource] processed_source
31
- def path_relative_to_source(processed_source)
32
- source_filename = processed_source.buffer.name
33
- rel_path = config.path_relative_to_config(source_filename)
34
- path = Pathname.new(rel_path).expand_path
35
- path.relative_path_from(source_path)
36
- end
26
+ # @return [Pathname]
27
+ def source_path
28
+ config_path.join(relative_source_path)
29
+ end
37
30
 
38
- # @param [RuboCop::ProcessedSource] processed_source
39
- def root_file?(processed_source)
40
- filename = path_relative_to_source(processed_source)
41
- filename.extname.downcase == '.rb' &&
42
- filename.parent.to_s == '.'
43
- end
31
+ # @param [RuboCop::ProcessedSource] processed_source
32
+ def path_relative_to_source(processed_source)
33
+ source_filename = processed_source.buffer.name
34
+ rel_path = config.path_relative_to_config(source_filename)
35
+ path = Pathname.new(rel_path).expand_path
36
+ path.relative_path_from(source_path)
37
+ end
44
38
 
39
+ # @param [RuboCop::ProcessedSource] processed_source
40
+ def root_file?(processed_source)
41
+ filename = path_relative_to_source(processed_source)
42
+ filename.extname.downcase == '.rb' &&
43
+ filename.parent.to_s == '.'
45
44
  end
45
+
46
46
  end
47
47
  end
48
48
  end
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'erb'
5
+ require 'ostruct'
6
+ require 'base64'
7
+ require 'rubocop/formatter/text_util'
8
+
9
+ module RuboCop
10
+ module Formatter
11
+ # This formatter saves the output as an html file.
12
+ class ExtensionReviewFormatter < BaseFormatter
13
+ ELLIPSES = '<span class="extra-code">...</span>'.freeze
14
+ TEMPLATE_PATH =
15
+ File.expand_path('../../../../../assets/output.html.erb', __FILE__)
16
+
17
+ Color = Struct.new(:red, :green, :blue, :alpha) do
18
+ def to_s
19
+ "rgba(#{values.join(', ')})"
20
+ end
21
+
22
+ def fade_out(amount)
23
+ dup.tap do |color|
24
+ color.alpha -= amount
25
+ end
26
+ end
27
+ end
28
+
29
+ attr_reader :categories, :files, :summary
30
+
31
+ def initialize(output, options = {})
32
+ super
33
+ @categories = {}
34
+ @files = []
35
+ @summary = OpenStruct.new(offense_count: 0)
36
+ end
37
+
38
+ def started(target_files)
39
+ summary.target_files = target_files
40
+ end
41
+
42
+ def file_finished(file, offenses)
43
+ files << file
44
+ offenses.each { |offense|
45
+ report = OpenStruct.new(path: file, offense: offense)
46
+ categories[offense.cop_name] ||= []
47
+ categories[offense.cop_name] << report
48
+ }
49
+ summary.offense_count += offenses.count
50
+ end
51
+
52
+ def finished(inspected_files)
53
+ summary.inspected_files = inspected_files
54
+
55
+ render_html
56
+ end
57
+
58
+ def render_html
59
+ context = ERBContext.new(categories, files, summary)
60
+
61
+ template = File.read(TEMPLATE_PATH, encoding: Encoding::UTF_8)
62
+ erb = ERB.new(template, nil, '-')
63
+ html = erb.result(context.binding)
64
+
65
+ output.write html
66
+ end
67
+
68
+ # This class provides helper methods used in the ERB template.
69
+ class ERBContext
70
+ include PathUtil
71
+ include TextUtil
72
+
73
+ SEVERITY_COLORS = {
74
+ refactor: Color.new(0xED, 0x9C, 0x28, 1.0),
75
+ convention: Color.new(0xED, 0x9C, 0x28, 1.0),
76
+ warning: Color.new(0x96, 0x28, 0xEF, 1.0),
77
+ error: Color.new(0xD2, 0x32, 0x2D, 1.0),
78
+ fatal: Color.new(0xD2, 0x32, 0x2D, 1.0)
79
+ }.freeze
80
+
81
+ LOGO_IMAGE_PATH =
82
+ File.expand_path('../../../../../assets/logo.png', __FILE__)
83
+
84
+ SORT_ORDER = %w[
85
+ SketchupRequirements
86
+ SketchupDeprecations
87
+ SketchupPerformance
88
+ SketchupSuggestions
89
+ ]
90
+
91
+ DEPARTMENT_DESCRIPTIONS = {
92
+ 'SketchupRequirements' => <<-DESCRIPTION,
93
+ This is the most important set of checks. They represent a large
94
+ part of the technical requirements an extension must pass in order
95
+ to be hosted on Extension Warehouse.
96
+
97
+ They have been designed to prevent extensions from conflicting with
98
+ each other as well as avoiding bad side-effects for the end user.
99
+
100
+ Please address these as soon as possible.
101
+ DESCRIPTION
102
+ 'SketchupDeprecations' => <<-DESCRIPTION,
103
+ This department checks for usage of deprecated features. It's
104
+ recommended that you migrate your code away from deprecated features
105
+ of the SketchUp API.
106
+
107
+ This department is not a requirement for submission to
108
+ Extension Warehouse.
109
+ DESCRIPTION
110
+ 'SketchupPerformance' => <<-DESCRIPTION,
111
+ This department looks for known patterns that have noticeable
112
+ performance impact on SketchUp and/or your extension. It's worth
113
+ looking into these warnings and investigate whether performance
114
+ can be improved.
115
+
116
+ This department is not a requirement for submission to
117
+ Extension Warehouse.
118
+ DESCRIPTION
119
+ 'SketchupSuggestions' => <<-DESCRIPTION,
120
+ This department is a collection of suggestions for best practices
121
+ that aim to improve the general quality of your extension. Some of
122
+ these might be more noisy than the rest of the cops. Disable as
123
+ needed after reviewing the suggestions.
124
+
125
+ This department is not a requirement for submission to
126
+ Extension Warehouse.
127
+ DESCRIPTION
128
+ }
129
+
130
+ attr_reader :categories, :files, :summary
131
+
132
+ def initialize(categories, files, summary)
133
+ @categories = sort_categories(categories)
134
+ @files = files.sort
135
+ @summary = summary
136
+ end
137
+
138
+ def department(cop_name)
139
+ cop_name.split('/').first
140
+ end
141
+
142
+ def department_description(cop_name)
143
+ dep = department(cop_name)
144
+ text = DEPARTMENT_DESCRIPTIONS[dep] || 'MISSING DESCRIPTION'
145
+ format_plain_text(text)
146
+ end
147
+
148
+ def department_offense_count(cop_name)
149
+ dep = department(cop_name)
150
+ count = 0
151
+ categories.each { |category, offenses|
152
+ next unless department(category) == dep
153
+ count += offenses.size
154
+ }
155
+ count
156
+ end
157
+
158
+ def new_department?(cop_name)
159
+ @processed_departments ||= Set.new
160
+ dep = department(cop_name)
161
+ unless @processed_departments.include?(dep)
162
+ @processed_departments << dep
163
+ return true
164
+ end
165
+ false
166
+ end
167
+
168
+ def format_plain_text(text)
169
+ paragraphs = text.split(/(\n\r|\r\n|\r|\n){2,}/m)
170
+ "<p>#{paragraphs.join('</p><p>')}</p>"
171
+ end
172
+
173
+ def sort_categories(categories)
174
+ categories.sort { |a, b|
175
+ # First sort departments by custom ordering (of importance).
176
+ # Then sort by cop name.
177
+ a_department, a_name = a[0].split('/')
178
+ b_department, b_name = b[0].split('/')
179
+ n = SORT_ORDER.index(a_department) <=> SORT_ORDER.index(b_department)
180
+ n == 0 ? a_name <=> b_name : n
181
+ }.to_h
182
+ end
183
+
184
+ # Make Kernel#binding public.
185
+ def binding
186
+ super
187
+ end
188
+
189
+ def decorated_message(offense)
190
+ offense.message.gsub(/`(.+?)`/) do
191
+ "<code>#{Regexp.last_match(1)}</code>"
192
+ end
193
+ end
194
+
195
+ def highlighted_source_line(offense)
196
+ source_before_highlight(offense) +
197
+ hightlight_source_tag(offense) +
198
+ source_after_highlight(offense) +
199
+ possible_ellipses(offense.location)
200
+ end
201
+
202
+ def hightlight_source_tag(offense)
203
+ "<span class=\"highlight #{offense.severity}\">" \
204
+ "#{escape(offense.highlighted_area.source)}" \
205
+ '</span>'
206
+ end
207
+
208
+ def source_before_highlight(offense)
209
+ source_line = offense.location.source_line
210
+ escape(source_line[0...offense.highlighted_area.begin_pos])
211
+ end
212
+
213
+ def source_after_highlight(offense)
214
+ source_line = offense.location.source_line
215
+ escape(source_line[offense.highlighted_area.end_pos..-1])
216
+ end
217
+
218
+ def possible_ellipses(location)
219
+ location.first_line == location.last_line ? '' : " #{ELLIPSES}"
220
+ end
221
+
222
+ def cop_anchor(cop_name)
223
+ title = cop_name.downcase
224
+ title.tr!('/', '_')
225
+ "offense_#{title}"
226
+ end
227
+
228
+ def escape(s)
229
+ CGI.escapeHTML(s)
230
+ end
231
+
232
+ def base64_encoded_logo_image
233
+ image = File.read(LOGO_IMAGE_PATH, binmode: true)
234
+ Base64.encode64(image)
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  def self.defaults!
8
8
  path = CONFIG_DEFAULT.to_s
9
9
  hash = ConfigLoader.send(:load_yaml_configuration, path)
10
- config = Config.new(hash, path)
10
+ config = RuboCop::Config.new(hash, path)
11
11
  puts "configuration from #{path}" if ConfigLoader.debug?
12
12
  config = ConfigLoader.merge_with_default(config, path)
13
13
  ConfigLoader.instance_variable_set(:@default_configuration, config)
@@ -1,48 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop
5
- module SketchUp
6
- class Namespace
7
-
8
- attr_reader :namespace
9
-
10
- SEPARATOR = '::'.freeze
11
-
12
- # @param [String] namespace
13
- def initialize(namespace)
14
- raise TypeError unless namespace.is_a?(String)
15
- @namespace = namespace
16
- end
17
-
18
- # Get the first component of a namespace relative to Object.
19
- # May return 'Object' if the namespace is in the global namespace.
20
- def first
21
- parts.find { |name| name != 'Object' } || 'Object'
22
- end
23
-
24
- # Get a namespace string that is relative to Object.
25
- def from_root
26
- items = parts
27
- items.shift if items.size > 1 && items.first == 'Object'
28
- items.join(SEPARATOR)
29
- end
30
-
31
- def join(other)
32
- self.class.new("#{@namespace}#{SEPARATOR}#{other}")
33
- end
34
-
35
- # Get the first component of a namespace relative to Object.
36
- # May return 'Object' if the namespace is in the global namespace.
37
- def parts
38
- namespace.split(SEPARATOR)
39
- end
40
-
41
- def top_level?
42
- parts.last == 'Object'
43
- end
4
+ module SketchUp
5
+ class Namespace
44
6
 
7
+ attr_reader :namespace
8
+
9
+ SEPARATOR = '::'.freeze
10
+
11
+ # @param [String] namespace
12
+ def initialize(namespace)
13
+ raise TypeError unless namespace.is_a?(String)
14
+ @namespace = namespace
15
+ end
16
+
17
+ # Get the first component of a namespace relative to Object.
18
+ # May return 'Object' if the namespace is in the global namespace.
19
+ def first
20
+ parts.find { |name| name != 'Object' } || 'Object'
45
21
  end
22
+
23
+ # Get a namespace string that is relative to Object.
24
+ def from_root
25
+ items = parts
26
+ items.shift if items.size > 1 && items.first == 'Object'
27
+ items.join(SEPARATOR)
28
+ end
29
+
30
+ def join(other)
31
+ self.class.new("#{@namespace}#{SEPARATOR}#{other}")
32
+ end
33
+
34
+ # Get the first component of a namespace relative to Object.
35
+ # May return 'Object' if the namespace is in the global namespace.
36
+ def parts
37
+ namespace.split(SEPARATOR)
38
+ end
39
+
40
+ def top_level?
41
+ parts.last == 'Object'
42
+ end
43
+
46
44
  end
47
45
  end
48
46
  end
@@ -1,45 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop
5
- module Sketchup
6
- module NamespaceChecker
7
-
8
- def on_class(node)
9
- check_namespace(node)
10
- end
11
-
12
- def on_module(node)
13
- check_namespace(node)
14
- end
15
-
16
- def on_def(node)
17
- check_namespace(node)
18
- end
19
- alias on_defs on_def
20
-
21
- # Constant assignment.
22
- def on_casgn(node)
23
- check_namespace(node)
24
- end
25
-
26
- def check_namespace(node)
27
- add_offense(node, location: :name, severity: :error) if in_namespace?(node)
28
- end
29
-
30
- def in_namespace?(node)
31
- # parent_module_name might return nil if for instance a method is
32
- # defined within a block. (Apparently that is possible...)
33
- return false if node.parent_module_name.nil?
34
- namespace = SketchUp::Namespace.new(node.parent_module_name)
35
- namespaces.include?(namespace.first)
36
- end
37
-
38
- def namespaces
39
- raise NotImplementedError
40
- end
4
+ module SketchUp
5
+ module NamespaceChecker
41
6
 
7
+ def on_class(node)
8
+ check_namespace(node)
42
9
  end
10
+
11
+ def on_module(node)
12
+ check_namespace(node)
13
+ end
14
+
15
+ def on_def(node)
16
+ check_namespace(node)
17
+ end
18
+ alias on_defs on_def
19
+
20
+ # Constant assignment.
21
+ def on_casgn(node)
22
+ check_namespace(node)
23
+ end
24
+
25
+ def check_namespace(node)
26
+ add_offense(node, location: :name, severity: :error) if in_namespace?(node)
27
+ end
28
+
29
+ def in_namespace?(node)
30
+ # parent_module_name might return nil if for instance a method is
31
+ # defined within a block. (Apparently that is possible...)
32
+ return false if node.parent_module_name.nil?
33
+ namespace = SketchUp::Namespace.new(node.parent_module_name)
34
+ namespaces.include?(namespace.first)
35
+ end
36
+
37
+ def namespaces
38
+ raise NotImplementedError
39
+ end
40
+
43
41
  end
44
42
  end
45
43
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop
4
+ module SketchUp
5
5
  module NoCommentDisable
6
6
 
7
7
  private
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module SketchupPerformance
6
6
  class Typename < Cop
7
- MSG = '.typename is very slow, prefer .is_a? instead.'.freeze
7
+ MSG = '`.typename` is very slow, prefer `.is_a?` instead.'.freeze
8
8
 
9
9
  def on_send(node)
10
10
  _, method_name = *node
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class ApiNamespace < Cop
7
7
 
8
- include NoCommentDisable
9
- include Sketchup::NamespaceChecker
8
+ include SketchUp::NoCommentDisable
9
+ include SketchUp::NamespaceChecker
10
10
 
11
11
  MSG = 'Do not modify the SketchUp API.'.freeze
12
12
 
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # Use `return`, `next` or `break` instead.
11
11
  class Exit < Cop
12
12
 
13
- include NoCommentDisable
13
+ include SketchUp::NoCommentDisable
14
14
 
15
15
  MSG = 'Exit attempts to kill the Ruby interpreter. Use return, next or break instead.'.freeze
16
16
 
@@ -20,7 +20,7 @@ module RuboCop
20
20
  PATTERN
21
21
 
22
22
  def on_send(node)
23
- add_offense(node, location: :expression) if exit?(node)
23
+ add_offense(node, location: :expression, severity: :error) if exit?(node)
24
24
  end
25
25
  end
26
26
  end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  module SketchupRequirements
10
10
  class ExtensionNamespace < Cop
11
11
 
12
- include NoCommentDisable
12
+ include SketchUp::NoCommentDisable
13
13
  include SketchUp
14
14
 
15
15
  def on_class(node)
@@ -55,7 +55,7 @@ module RuboCop
55
55
 
56
56
  def message(node)
57
57
  namespace = Namespace.new(node.defined_module_name).from_root
58
- format('Use a single root namespace. (Found %s; Previously found %s)', namespace, @@namespace)
58
+ format('Use a single root namespace. (Found `%s`; Previously found `%s`)', namespace, @@namespace)
59
59
  end
60
60
 
61
61
  end
@@ -7,8 +7,8 @@ module RuboCop
7
7
  # single root .rb file and a support folder with matching name.
8
8
  class FileStructure < Cop
9
9
 
10
- include Sketchup::ExtensionProject
11
- include NoCommentDisable
10
+ include SketchUp::NoCommentDisable
11
+ include SketchUp::ExtensionProject
12
12
 
13
13
  IGNORED_DIRECTORIES = %w[
14
14
  __MACOSX
@@ -27,7 +27,10 @@ module RuboCop
27
27
  # Ensure there is only one root Ruby file.
28
28
  if root_ruby_files.size != 1
29
29
  msg = "Extensions must have exactly one root Ruby (.rb) file. Found: %d"
30
- add_offense(nil, location: range, message: format(msg, root_ruby_files.size))
30
+ add_offense(nil,
31
+ location: range,
32
+ message: format(msg, root_ruby_files.size),
33
+ severity: :error)
31
34
  return
32
35
  end
33
36
 
@@ -42,7 +45,10 @@ module RuboCop
42
45
  # Ensure there is only one sub-directory.
43
46
  if sub_folders.size != 1
44
47
  msg = "Extensions must have exactly one support directory. Found %d"
45
- add_offense(nil, location: range, message: format(msg, sub_folders.size))
48
+ add_offense(nil,
49
+ location: range,
50
+ message: format(msg, sub_folders.size),
51
+ severity: :error)
46
52
  return
47
53
  end
48
54
 
@@ -51,7 +57,10 @@ module RuboCop
51
57
  unless support_directory.basename.to_s == extension_basename
52
58
  msg = 'Extensions must have a support directory matching the name of the root Ruby file. Expected %s, found %s'
53
59
  msg = format(msg, extension_basename, support_directory.basename)
54
- add_offense(nil, location: range, message: msg)
60
+ add_offense(nil,
61
+ location: range,
62
+ message: msg,
63
+ severity: :error)
55
64
  end
56
65
  end
57
66
 
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class GlobalConstants < Cop
7
7
 
8
- include NoCommentDisable
8
+ include SketchUp::NoCommentDisable
9
9
  include SketchUp
10
10
 
11
11
  MSG = 'Do not introduce global constants.'.freeze
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class GlobalMethods < Cop
7
7
 
8
- include NoCommentDisable
8
+ include SketchUp::NoCommentDisable
9
9
  include SketchUp
10
10
 
11
11
  MSG = 'Do not introduce global methods.'.freeze
@@ -10,12 +10,15 @@ module RuboCop
10
10
  #
11
11
  # Note that backreferences like $1, $2, etc are not global variables.
12
12
  class GlobalVariables < Cop
13
- include NoCommentDisable
13
+
14
+ include SketchUp::NoCommentDisable
15
+ include SketchUp::DynamicComponentGlobals
16
+
14
17
  MSG = 'Do not introduce global variables.'.freeze
15
18
 
16
19
  # predefined global variables their English aliases
17
20
  # http://www.zenspider.com/Languages/Ruby/QuickRef.html
18
- BUILT_IN_VARS = %w(
21
+ BUILT_IN_VARS = %i[
19
22
  $: $LOAD_PATH
20
23
  $" $LOADED_FEATURES
21
24
  $0 $PROGRAM_NAME
@@ -42,21 +45,30 @@ module RuboCop
42
45
  $DEBUG $FILENAME $VERBOSE $SAFE
43
46
  $-0 $-a $-d $-F $-i $-I $-l $-p $-v $-w
44
47
  $CLASSPATH $JRUBY_VERSION $JRUBY_REVISION $ENV_JAVA
45
- ).map(&:to_sym)
48
+ ]
46
49
 
47
- # TODO: This should probably be read only.
48
- SKETCHUP_VARS = %w(
50
+ SKETCHUP_VARS = %i[
49
51
  $loaded_files
50
- ).map(&:to_sym)
52
+ ]
53
+
54
+ # Some globals, like DC's, are being read from so often that it's better
55
+ # to ignore these to reduce noise.
56
+ READ_ONLY_VARS = DC_GLOBALS
51
57
 
52
58
  ALLOWED_VARS = BUILT_IN_VARS | SKETCHUP_VARS
53
59
 
60
+
54
61
  def allowed_var?(global_var)
55
62
  ALLOWED_VARS.include?(global_var)
56
63
  end
57
64
 
65
+ def read_allowed?(global_var)
66
+ READ_ONLY_VARS.include?(global_var)
67
+ end
68
+
58
69
  def on_gvar(node)
59
- check(node)
70
+ global_var, = *node
71
+ check(node) unless read_allowed?(global_var)
60
72
  end
61
73
 
62
74
  def on_gvasgn(node)
@@ -8,6 +8,8 @@ module RuboCop
8
8
  # They are still in use due to compatibility reasons.
9
9
  class LanguageHandlerGlobals < Cop
10
10
 
11
+ include SketchUp::NoCommentDisable
12
+
11
13
  MSG = "Avoid globals in general, but especially these which are known to be in use.".freeze
12
14
 
13
15
  LH_GLOBALS = %i[
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class LoadPath < Cop
7
7
 
8
- include NoCommentDisable
8
+ include SketchUp::NoCommentDisable
9
9
 
10
10
  MSG = 'Do not modify the load path.'.freeze
11
11
 
@@ -9,8 +9,8 @@ module RuboCop
9
9
  # Extensions should not load additional files when it's disabled.
10
10
  class MinimalRegistration < Cop
11
11
 
12
- include Sketchup::ExtensionProject
13
- include NoCommentDisable
12
+ include SketchUp::NoCommentDisable
13
+ include SketchUp::ExtensionProject
14
14
 
15
15
  MSG = "Don't load extension files in the root file registering the extension.".freeze
16
16
 
@@ -39,7 +39,7 @@ module RuboCop
39
39
  filename = require_filename(node)
40
40
  return if filename.nil?
41
41
  return unless extension_file?(filename)
42
- add_offense(node)
42
+ add_offense(node, severity: :error)
43
43
  end
44
44
 
45
45
  end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  # confuse users to think the extension isn't working.
8
8
  class RegisterExtension < Cop
9
9
 
10
- include NoCommentDisable
10
+ include SketchUp::NoCommentDisable
11
11
 
12
12
  MSG = 'Always register extensions to load by default.'.freeze
13
13
 
@@ -20,12 +20,12 @@ module RuboCop
20
20
  def on_send(node)
21
21
  sketchup_register_extension(node).each { |args|
22
22
  if args.size < 2
23
- add_offense(node)
23
+ add_offense(node, severity: :error)
24
24
  next
25
25
  end
26
26
  load_arg = args[1]
27
27
  next if load_arg.true_type?
28
- add_offense(load_arg)
28
+ add_offense(load_arg, severity: :error)
29
29
  }
30
30
  end
31
31
 
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupRequirements
6
+ # Due to how require and Sketchup.require checks whether a file has been
7
+ # loaded, files from SketchUp's Tools folder should be required in using
8
+ # `require` and with their file extension to avoid duplicate loading.
9
+ class RequireToolsRubyFiles < Cop
10
+
11
+ include SketchUp::NoCommentDisable
12
+
13
+ MSG = "Require files from SketchUp's Tools directory with require.".freeze
14
+
15
+ RUBY_FILES = %w[extensions.rb langhandler.rb sketchup.rb]
16
+
17
+
18
+ def_node_matcher :ruby_require, <<-PATTERN
19
+ (send nil? :require (str $_))
20
+ PATTERN
21
+
22
+ def_node_matcher :ruby_require?, <<-PATTERN
23
+ (send nil? :require (str _))
24
+ PATTERN
25
+
26
+
27
+ def_node_matcher :sketchup_require, <<-PATTERN
28
+ (send
29
+ (const nil? :Sketchup) :require
30
+ (str $_)
31
+ )
32
+ PATTERN
33
+
34
+ def_node_matcher :sketchup_require?, <<-PATTERN
35
+ (send
36
+ (const nil? :Sketchup) :require
37
+ (str _)
38
+ )
39
+ PATTERN
40
+
41
+
42
+ def on_send(node)
43
+ if ruby_require?(node)
44
+ # The Tools folder Ruby files should be loaded using `require` and
45
+ # include the ".rb" file extension.
46
+ filename = ruby_require(node)
47
+ return unless tools_file_required?(filename)
48
+ return if expected_require_syntax?(filename)
49
+ add_offense(node, location: :expression, severity: :error)
50
+ elsif sketchup_require?(node)
51
+ # The Tools folder Ruby files should not be loaded using
52
+ # `Sketchup.require`.
53
+ filename = sketchup_require(node)
54
+ return unless tools_file_required?(filename)
55
+ add_offense(node, location: :expression, severity: :error)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def tools_file_required?(filename)
62
+ ext = File.extname(filename)
63
+ full_filename = ext.empty? ? "#{filename}.rb" : filename
64
+ RUBY_FILES.include?(full_filename.downcase)
65
+ end
66
+
67
+ def expected_require_syntax?(filename)
68
+ RUBY_FILES.include?(filename)
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+ end
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class RubyCoreNamespace < Cop
7
7
 
8
- include NoCommentDisable
9
- include Sketchup::NamespaceChecker
8
+ include SketchUp::NoCommentDisable
9
+ include SketchUp::NamespaceChecker
10
10
 
11
11
  MSG = 'Do not modify Ruby core functionality.'.freeze
12
12
 
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class RubyStdLibNamespace < Cop
7
7
 
8
- include NoCommentDisable
9
- include Sketchup::NamespaceChecker
8
+ include SketchUp::NoCommentDisable
9
+ include SketchUp::NamespaceChecker
10
10
 
11
11
  MSG = 'Do not modify Ruby stdlib functionality.'.freeze
12
12
 
@@ -6,8 +6,8 @@ module RuboCop
6
6
  # Don't modify SketchUp's shipped extensions.
7
7
  class ShippedExtensionsNamespace < Cop
8
8
 
9
- include NoCommentDisable
10
- include Sketchup::NamespaceChecker
9
+ include SketchUp::NoCommentDisable
10
+ include SketchUp::NamespaceChecker
11
11
 
12
12
  MSG = 'Do not modify shipped extensions.'.freeze
13
13
 
@@ -7,8 +7,8 @@ module RuboCop
7
7
  # This should be done by the root .rb file in the extension package.
8
8
  class SketchupExtension < Cop
9
9
 
10
- include Sketchup::ExtensionProject
11
- include NoCommentDisable
10
+ include SketchUp::NoCommentDisable
11
+ include SketchUp::ExtensionProject
12
12
 
13
13
  MSG = 'Create and register one SketchupExtension instance per extension.'.freeze
14
14
  MSG_CREATE_ONE = 'Create only SketchupExtension instance per extension.'.freeze
@@ -42,12 +42,18 @@ module RuboCop
42
42
  # Look for SketchupExtension.new.
43
43
  extension_nodes = sketchup_extension_new(source_node).to_a
44
44
  if extension_nodes.size > 1
45
- add_offense(nil, location: range, message: MSG_CREATE_ONE)
45
+ add_offense(nil,
46
+ location: range,
47
+ message: MSG_CREATE_ONE,
48
+ severity: :error)
46
49
  return
47
50
  end
48
51
  extension_node = extension_nodes.first
49
52
  if extension_node.nil?
50
- add_offense(nil, location: range, message: MSG_CREATE_MISSING)
53
+ add_offense(nil,
54
+ location: range,
55
+ message: MSG_CREATE_MISSING,
56
+ severity: :error)
51
57
  return
52
58
  end
53
59
 
@@ -64,13 +70,19 @@ module RuboCop
64
70
  # TODO: The offences here should probably highlight the line where
65
71
  # Sketchup.register_extension is.
66
72
  if registered_vars.size > 1
67
- add_offense(nil, location: range, message: MSG_REGISTER_ONE)
73
+ add_offense(nil,
74
+ location: range,
75
+ message: MSG_REGISTER_ONE,
76
+ severity: :error)
68
77
  return
69
78
  end
70
79
  registered_var = sketchup_register_extension(source_node).first
71
80
  unless registered_var == extension_var
72
81
  msg = MSG_REGISTER_MISSING % extension_var.to_s
73
- add_offense(nil, location: range, message: msg)
82
+ add_offense(nil,
83
+ location: range,
84
+ message: msg,
85
+ severity: :error)
74
86
  end
75
87
  end
76
88
 
@@ -7,20 +7,9 @@ module RuboCop
7
7
  # change at any time.
8
8
  class DynamicComponentInternals < Cop
9
9
 
10
- MSG = "Avoid relying on internal logic of Dynamic Components.".freeze
11
-
12
- DC_GLOBALS = %i[
13
- $dc_strings
14
- $dc_extension
15
- $dc_CONFIGURATOR_NAME
16
- $dc_REPORTER_NAME
17
- $dc_MANAGER_NAME
18
- $dc_observers
19
- ]
10
+ include SketchUp::DynamicComponentGlobals
20
11
 
21
- def dc_global_var?(global_var)
22
- DC_GLOBALS.include?(global_var)
23
- end
12
+ MSG = "Avoid relying on internal logic of Dynamic Components.".freeze
24
13
 
25
14
  def on_gvar(node)
26
15
  check(node)
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # basename = File.basename(file, '.*')
18
18
  class FileEncoding < Cop
19
19
 
20
- MSG = 'Beware encoding bug with __FILE__ and __dir__.'.freeze
20
+ MSG = 'Beware encoding bug with `__FILE__` and `__dir__`.'.freeze
21
21
 
22
22
  def_node_matcher :file_loaded?, <<-PATTERN
23
23
  (send nil? {:file_loaded? :file_loaded} ...)
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module SketchupSuggestions
6
6
  class ModelEntities < Cop
7
+
7
8
  MSG = 'Typically one should use model.active_entities instead of model.entities.'.freeze
8
9
 
9
10
  # Reference: http://www.rubydoc.info/gems/rubocop/RuboCop/NodePattern
@@ -17,7 +17,7 @@ module RuboCop
17
17
  return unless operation_name.is_a?(String)
18
18
  # Check the format of the operation name.
19
19
  unless acceptable_operation_name?(operation_name)
20
- msg = %[#{MSG} Expected: "#{titleize(operation_name)}"]
20
+ msg = %[#{MSG} Expected: `"#{titleize(operation_name)}"`]
21
21
  add_offense(args.first, location: :expression, message: msg)
22
22
  end
23
23
  # Check the length of the operation name.
@@ -1,5 +1,5 @@
1
1
  module RuboCop
2
2
  module SketchUp
3
- VERSION = '0.1.5'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
 
14
14
  spec.version = RuboCop::SketchUp::VERSION
15
15
  spec.platform = Gem::Platform::RUBY
16
- spec.required_ruby_version = '>= 2.0.0'
16
+ spec.required_ruby_version = '>= 2.1.0'
17
17
 
18
18
  spec.require_paths = ['lib']
19
19
  spec.files = Dir[
@@ -23,6 +23,8 @@ Gem::Specification.new do |spec|
23
23
  'Gemfile'
24
24
  ]
25
25
 
26
- spec.add_dependency 'rubocop', '~> 0.51.0'
26
+ # NOTE: Remember to update the README.md instructions on how to install
27
+ # compatible RuboCop version.
28
+ spec.add_dependency 'rubocop', '~> 0.52.0'
27
29
  spec.add_development_dependency 'bundler', '~> 1.13'
28
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-sketchup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Trimble Inc, SketchUp Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-08 00:00:00.000000000 Z
11
+ date: 2018-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.51.0
19
+ version: 0.52.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.51.0
26
+ version: 0.52.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -48,8 +48,11 @@ files:
48
48
  - config/default.yml
49
49
  - lib/rubocop-sketchup.rb
50
50
  - lib/rubocop/sketchup.rb
51
+ - lib/rubocop/sketchup/config.rb
52
+ - lib/rubocop/sketchup/dc_globals.rb
51
53
  - lib/rubocop/sketchup/deprecations/operation_next_transparent.rb
52
54
  - lib/rubocop/sketchup/extension_project.rb
55
+ - lib/rubocop/sketchup/formatter/extension_review.rb
53
56
  - lib/rubocop/sketchup/inject.rb
54
57
  - lib/rubocop/sketchup/namespace.rb
55
58
  - lib/rubocop/sketchup/namespace_checker.rb
@@ -69,6 +72,7 @@ files:
69
72
  - lib/rubocop/sketchup/requirements/load_path.rb
70
73
  - lib/rubocop/sketchup/requirements/minimal_registration.rb
71
74
  - lib/rubocop/sketchup/requirements/register_extension.rb
75
+ - lib/rubocop/sketchup/requirements/require_tools_ruby_files.rb
72
76
  - lib/rubocop/sketchup/requirements/ruby_core_namespace.rb
73
77
  - lib/rubocop/sketchup/requirements/ruby_stdlib_namespace.rb
74
78
  - lib/rubocop/sketchup/requirements/shipped_extensions_namespace.rb
@@ -93,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
93
97
  requirements:
94
98
  - - ">="
95
99
  - !ruby/object:Gem::Version
96
- version: 2.0.0
100
+ version: 2.1.0
97
101
  required_rubygems_version: !ruby/object:Gem::Requirement
98
102
  requirements:
99
103
  - - ">="
@@ -101,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
105
  version: '0'
102
106
  requirements: []
103
107
  rubyforge_project:
104
- rubygems_version: 2.5.2
108
+ rubygems_version: 2.6.14
105
109
  signing_key:
106
110
  specification_version: 4
107
111
  summary: RuboCop rules for SketchUp extensions.