rubocop-sketchup 0.2.1 → 0.3.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/Gemfile +1 -0
- data/config/default.yml +22 -0
- data/lib/rubocop-sketchup.rb +3 -0
- data/lib/rubocop/sketchup/config.rb +12 -0
- data/lib/rubocop/sketchup/dc_methods.rb +130 -0
- data/lib/rubocop/sketchup/features.rb +738 -0
- data/lib/rubocop/sketchup/inject.rb +2 -0
- data/lib/rubocop/sketchup/namespace.rb +1 -1
- data/lib/rubocop/sketchup/requirements/extension_namespace.rb +15 -1
- data/lib/rubocop/sketchup/requirements/file_structure.rb +1 -0
- data/lib/rubocop/sketchup/requirements/global_include.rb +35 -0
- data/lib/rubocop/sketchup/requirements/minimal_registration.rb +1 -1
- data/lib/rubocop/sketchup/requirements/observers_start_operation.rb +135 -0
- data/lib/rubocop/sketchup/requirements/register_extension.rb +1 -1
- data/lib/rubocop/sketchup/requirements/sketchup_extension.rb +22 -14
- data/lib/rubocop/sketchup/sketchup_version.rb +85 -0
- data/lib/rubocop/sketchup/suggestions/compatibility.rb +89 -0
- data/lib/rubocop/sketchup/suggestions/dc_internals.rb +3 -3
- data/lib/rubocop/sketchup/suggestions/file_encoding.rb +1 -4
- data/lib/rubocop/sketchup/suggestions/monkey_patched_api.rb +41 -0
- data/lib/rubocop/sketchup/suggestions/operation_name.rb +2 -0
- data/lib/rubocop/sketchup/suggestions/sketchup_require.rb +24 -2
- data/lib/rubocop/sketchup/version.rb +1 -1
- data/rubocop-sketchup.gemspec +1 -1
- metadata +11 -4
@@ -23,9 +23,12 @@ module RuboCop
|
|
23
23
|
def check_class_or_module(node)
|
24
24
|
name = node.defined_module_name
|
25
25
|
parent = Namespace.new(node.parent_module_name)
|
26
|
+
namespace = parent.join(name)
|
26
27
|
# Don't want to process anything that aren't top level namespaces.
|
27
28
|
return unless parent.top_level?
|
28
|
-
|
29
|
+
# Don't check excluded namespaces.
|
30
|
+
return if exempted?(namespace)
|
31
|
+
check_namespace(node, namespace)
|
29
32
|
end
|
30
33
|
|
31
34
|
# Class variables are normally frowned upon since they leak through all
|
@@ -58,6 +61,17 @@ module RuboCop
|
|
58
61
|
format('Use a single root namespace. (Found `%s`; Previously found `%s`)', namespace, @@namespace)
|
59
62
|
end
|
60
63
|
|
64
|
+
def exempted?(namespace)
|
65
|
+
namespace_exceptions.include?(namespace.first)
|
66
|
+
end
|
67
|
+
|
68
|
+
def namespace_exceptions
|
69
|
+
exceptions = cop_config['Exceptions'] || []
|
70
|
+
return exceptions if exceptions.is_a?(Array)
|
71
|
+
|
72
|
+
raise 'exceptions needs to be an array of strings!'
|
73
|
+
end
|
74
|
+
|
61
75
|
end
|
62
76
|
end
|
63
77
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module SketchupRequirements
|
6
|
+
class GlobalInclude < Cop
|
7
|
+
|
8
|
+
include SketchUp::NoCommentDisable
|
9
|
+
include SketchUp
|
10
|
+
|
11
|
+
MSG = 'Do not include into global namespace.'.freeze
|
12
|
+
|
13
|
+
def_node_matcher :is_include?, <<-PATTERN
|
14
|
+
(send nil? :include ...)
|
15
|
+
PATTERN
|
16
|
+
|
17
|
+
def on_send(node)
|
18
|
+
return unless global_include?(node)
|
19
|
+
add_offense(node, severity: :error)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def global_include?(node)
|
25
|
+
is_include?(node) && global_namespace?(node)
|
26
|
+
end
|
27
|
+
|
28
|
+
def global_namespace?(node)
|
29
|
+
['Kernel', 'Object'].include?(node.parent_module_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module SketchupRequirements
|
6
|
+
class ObserversStartOperation < Cop
|
7
|
+
|
8
|
+
include SketchUp::NoCommentDisable
|
9
|
+
include RangeHelp
|
10
|
+
|
11
|
+
MSG = 'Observers should create transparent operations.'.freeze
|
12
|
+
|
13
|
+
def_node_search :start_operation, <<-PATTERN
|
14
|
+
(send
|
15
|
+
_ :start_operation
|
16
|
+
...)
|
17
|
+
PATTERN
|
18
|
+
|
19
|
+
OBSERVER_METHODS = %i[
|
20
|
+
onActivateModel
|
21
|
+
onNewModel
|
22
|
+
onOpenModel
|
23
|
+
onQuit
|
24
|
+
onUnloadExtension
|
25
|
+
|
26
|
+
onComponentInstanceAdded
|
27
|
+
onComponentInstanceRemoved
|
28
|
+
|
29
|
+
onComponentAdded
|
30
|
+
onComponentPropertiesChanged
|
31
|
+
onComponentRemoved
|
32
|
+
onComponentTypeChanged
|
33
|
+
|
34
|
+
onTextChanged
|
35
|
+
|
36
|
+
onActiveSectionPlaneChanged
|
37
|
+
onElementAdded
|
38
|
+
onElementModified
|
39
|
+
onElementRemoved
|
40
|
+
onEraseEntities
|
41
|
+
|
42
|
+
onChangeEntity
|
43
|
+
onEraseEntity
|
44
|
+
|
45
|
+
onClose
|
46
|
+
onOpen
|
47
|
+
|
48
|
+
onCurrentLayerChanged
|
49
|
+
onLayerAdded
|
50
|
+
onLayerChanged
|
51
|
+
onLayerRemoved
|
52
|
+
onRemoveAllLayers
|
53
|
+
|
54
|
+
onMaterialAdd
|
55
|
+
onMaterialChange
|
56
|
+
onMaterialRefChange
|
57
|
+
onMaterialRemove
|
58
|
+
onMaterialSetCurrent
|
59
|
+
onMaterialUndoRedo
|
60
|
+
|
61
|
+
onActivePathChanged
|
62
|
+
onAfterComponentSaveAs
|
63
|
+
onBeforeComponentSaveAs
|
64
|
+
onDeleteModel
|
65
|
+
onEraseAll
|
66
|
+
onExplode
|
67
|
+
onPidChanged
|
68
|
+
onPlaceComponent
|
69
|
+
onPostSaveModel
|
70
|
+
onPreSaveModel
|
71
|
+
onSaveModel
|
72
|
+
onTransactionAbort
|
73
|
+
onTransactionCommit
|
74
|
+
onTransactionEmpty
|
75
|
+
onTransactionRedo
|
76
|
+
onTransactionStart
|
77
|
+
onTransactionUndo
|
78
|
+
|
79
|
+
onOptionsProviderChanged
|
80
|
+
|
81
|
+
onContentsModified
|
82
|
+
onElementAdded
|
83
|
+
onElementRemoved
|
84
|
+
|
85
|
+
onRenderingOptionsChanged
|
86
|
+
|
87
|
+
onSelectionAdded
|
88
|
+
onSelectionBulkChange
|
89
|
+
onSelectionCleared
|
90
|
+
onSelectionRemoved
|
91
|
+
|
92
|
+
onShadowInfoChanged
|
93
|
+
|
94
|
+
onActiveToolChanged
|
95
|
+
onToolStateChanged
|
96
|
+
|
97
|
+
onViewChanged
|
98
|
+
]
|
99
|
+
|
100
|
+
def on_def(node)
|
101
|
+
return unless observer_event?(node)
|
102
|
+
operations = start_operation(node)
|
103
|
+
operations.each { |operation|
|
104
|
+
_name, _disable_ui, _next_tr, transparent = operation.arguments
|
105
|
+
next unless transparent.nil? || transparent.falsey_literal?
|
106
|
+
location = operation_location(operation)
|
107
|
+
add_offense(operation, location: location, severity: :error)
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def range(node)
|
114
|
+
range_between(node.begin_pos, node.end_pos)
|
115
|
+
end
|
116
|
+
|
117
|
+
def operation_location(node)
|
118
|
+
# Highlight the fourth argument if it's used. Fall back to the method
|
119
|
+
# name.
|
120
|
+
transparent_argument = node.arguments[3]
|
121
|
+
if transparent_argument
|
122
|
+
range(transparent_argument.loc.expression)
|
123
|
+
else
|
124
|
+
:selector
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def observer_event?(node)
|
129
|
+
OBSERVER_METHODS.include?(node.method_name)
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -9,6 +9,7 @@ module RuboCop
|
|
9
9
|
|
10
10
|
include SketchUp::NoCommentDisable
|
11
11
|
include SketchUp::ExtensionProject
|
12
|
+
include RangeHelp
|
12
13
|
|
13
14
|
MSG = 'Create and register one SketchupExtension instance per extension.'.freeze
|
14
15
|
MSG_CREATE_ONE = 'Create only SketchupExtension instance per extension.'.freeze
|
@@ -18,17 +19,14 @@ module RuboCop
|
|
18
19
|
|
19
20
|
# Reference: http://rubocop.readthedocs.io/en/latest/node_pattern/
|
20
21
|
def_node_search :sketchup_extension_new, <<-PATTERN
|
21
|
-
(
|
22
|
-
(:
|
23
|
-
(:const nil? :SketchupExtension) :new
|
24
|
-
_
|
25
|
-
_))
|
22
|
+
(send
|
23
|
+
(const nil? :SketchupExtension) :new _ _)
|
26
24
|
PATTERN
|
27
25
|
|
28
26
|
def_node_search :sketchup_register_extension, <<-PATTERN
|
29
|
-
(
|
27
|
+
(send
|
30
28
|
(const nil? :Sketchup) :register_extension
|
31
|
-
{({
|
29
|
+
{({lvar ivar cvar gvar} $_)(const nil? $_)}
|
32
30
|
_)
|
33
31
|
PATTERN
|
34
32
|
|
@@ -41,6 +39,13 @@ module RuboCop
|
|
41
39
|
|
42
40
|
# Look for SketchupExtension.new.
|
43
41
|
extension_nodes = sketchup_extension_new(source_node).to_a
|
42
|
+
|
43
|
+
# Threat instances not assigned to anything as non-existing.
|
44
|
+
extension_nodes.select! { |node|
|
45
|
+
node.parent && node.parent.assignment?
|
46
|
+
}
|
47
|
+
|
48
|
+
# There should not be multiple instances.
|
44
49
|
if extension_nodes.size > 1
|
45
50
|
add_offense(nil,
|
46
51
|
location: range,
|
@@ -48,6 +53,8 @@ module RuboCop
|
|
48
53
|
severity: :error)
|
49
54
|
return
|
50
55
|
end
|
56
|
+
|
57
|
+
# There should be exactly one.
|
51
58
|
extension_node = extension_nodes.first
|
52
59
|
if extension_node.nil?
|
53
60
|
add_offense(nil,
|
@@ -58,24 +65,25 @@ module RuboCop
|
|
58
65
|
end
|
59
66
|
|
60
67
|
# Find the name of the value SketchupExtension.new was assigned to.
|
61
|
-
|
62
|
-
|
68
|
+
assignment_node = extension_node.parent
|
69
|
+
if assignment_node.casgn_type?
|
70
|
+
extension_var = assignment_node.to_a[1]
|
63
71
|
else
|
64
|
-
extension_var =
|
72
|
+
extension_var = assignment_node.to_a[0]
|
65
73
|
end
|
66
74
|
|
67
75
|
# Look for Sketchup.register and make sure it register the extension
|
68
76
|
# object detected earlier.
|
69
77
|
registered_vars = sketchup_register_extension(source_node).to_a
|
70
|
-
|
71
|
-
#
|
78
|
+
|
79
|
+
# Make sure there is only one call to `register_extension`.
|
72
80
|
if registered_vars.size > 1
|
73
|
-
add_offense(
|
74
|
-
location: range,
|
81
|
+
add_offense(registered_vars[1],
|
75
82
|
message: MSG_REGISTER_ONE,
|
76
83
|
severity: :error)
|
77
84
|
return
|
78
85
|
end
|
86
|
+
|
79
87
|
registered_var = sketchup_register_extension(source_node).first
|
80
88
|
unless registered_var == extension_var
|
81
89
|
msg = MSG_REGISTER_MISSING % extension_var.to_s
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module SketchUp
|
5
|
+
class SketchUpVersion
|
6
|
+
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
attr_reader :version, :maintenance
|
10
|
+
|
11
|
+
class InvalidVersion < StandardError; end
|
12
|
+
|
13
|
+
def initialize(version)
|
14
|
+
@version, @maintenance = parse_version(version)
|
15
|
+
end
|
16
|
+
|
17
|
+
def <=>(other)
|
18
|
+
if version == other.version
|
19
|
+
maintenance <=> other.maintenance
|
20
|
+
else
|
21
|
+
version <=> other.version
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
string_version = version < 2013 ? version.to_f : version.to_i
|
27
|
+
if maintenance > 0
|
28
|
+
"SketchUp #{string_version} M#{maintenance}"
|
29
|
+
else
|
30
|
+
"SketchUp #{string_version}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
VERSION_NUMBER_REGEX = /^(?:SketchUp )?([0-9.]+)(?: M(\d+))?$/
|
37
|
+
|
38
|
+
# This list is compiled from the list of versions reported by YARD when
|
39
|
+
# running the `versions` template;
|
40
|
+
#
|
41
|
+
# yardoc -t versions -f text
|
42
|
+
#
|
43
|
+
# TODO(thomthom): Push the version template to the API stubs repository.
|
44
|
+
VALID_VERSIONS = [
|
45
|
+
[2018, 0],
|
46
|
+
[2017, 0],
|
47
|
+
[2016, 1],
|
48
|
+
[2016, 0],
|
49
|
+
[2015, 0],
|
50
|
+
[2014, 0],
|
51
|
+
[2013, 0],
|
52
|
+
[8.0, 2],
|
53
|
+
[8.0, 1],
|
54
|
+
[8.0, 0],
|
55
|
+
[7.1, 1],
|
56
|
+
[7.1, 0],
|
57
|
+
[7.0, 1],
|
58
|
+
[7.0, 0],
|
59
|
+
[6.0, 0],
|
60
|
+
]
|
61
|
+
|
62
|
+
def parse_version(version)
|
63
|
+
v = 0
|
64
|
+
m = 0
|
65
|
+
if version.is_a?(String)
|
66
|
+
# Treat all LayOut versions as SketchUp versions for now.
|
67
|
+
normalised_version = version.gsub('LayOut', 'SketchUp')
|
68
|
+
result = normalised_version.match(VERSION_NUMBER_REGEX)
|
69
|
+
if result
|
70
|
+
v = result.captures[0].to_f
|
71
|
+
m = (result.captures[1] || '0').to_i
|
72
|
+
end
|
73
|
+
elsif version.is_a?(Numeric)
|
74
|
+
v, m = [version, 0]
|
75
|
+
end
|
76
|
+
version_parts = [v, m]
|
77
|
+
unless VALID_VERSIONS.include?(version_parts)
|
78
|
+
raise InvalidVersion, "#{version} is not a valid SketchUp version"
|
79
|
+
end
|
80
|
+
version_parts
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module SketchupSuggestions
|
6
|
+
class Compatibility < Cop
|
7
|
+
|
8
|
+
include SketchUp::Config
|
9
|
+
include SketchUp::Features
|
10
|
+
|
11
|
+
MSG = "Incompatible feature with target SketchUp version".freeze
|
12
|
+
|
13
|
+
def on_def(node)
|
14
|
+
return unless observer_method?(node)
|
15
|
+
feature_name = "##{node.method_name}"
|
16
|
+
check_feature(node, :method, feature_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_send(node)
|
20
|
+
if module_method?(node)
|
21
|
+
feature_name = "#{node.receiver.const_name}.#{node.method_name}"
|
22
|
+
check_feature(node, :method, feature_name)
|
23
|
+
else
|
24
|
+
# Instance methods are harder. It's difficult to infer the type of
|
25
|
+
# the receiver. If we only check the method name in isolation we
|
26
|
+
# will get too many false positives with method names matching
|
27
|
+
# methods in Ruby itself and other older features.
|
28
|
+
# We try to match names that are unlikely to cause much noise.
|
29
|
+
return unless checkable_instance_method?(node)
|
30
|
+
feature_name = "##{node.method_name}"
|
31
|
+
check_feature(node, :method, feature_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_const(node)
|
36
|
+
feature_name = node.const_name
|
37
|
+
[:class, :module, :constant].each { |type|
|
38
|
+
check_feature(node, type, feature_name)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def check_feature(node, type, feature_name)
|
45
|
+
return unless sketchup_target_version?
|
46
|
+
full_feature_name = feature_name
|
47
|
+
FEATURES.each { |feature_set|
|
48
|
+
feature_version = SketchUp::SketchUpVersion.new(feature_set[:version])
|
49
|
+
next unless feature_version > sketchup_target_version
|
50
|
+
objects = feature_set[:types][type] || []
|
51
|
+
if type == :method && instance_method?(feature_name)
|
52
|
+
# Instance methods are simply matching the method name since it's
|
53
|
+
# very difficult to determine the type of the receiver.
|
54
|
+
full_feature_name = objects.find { |object| object.end_with?(feature_name) }
|
55
|
+
next unless full_feature_name
|
56
|
+
else
|
57
|
+
next unless objects.include?(feature_name)
|
58
|
+
end
|
59
|
+
report(node, full_feature_name, feature_version, type)
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def report(node, feature_name, feature_version, feature_type)
|
64
|
+
message = "The #{feature_type} `#{feature_name}` was added in "\
|
65
|
+
"#{feature_version} which is incompatible with target "\
|
66
|
+
"#{sketchup_target_version}."
|
67
|
+
add_offense(node, message: message)
|
68
|
+
end
|
69
|
+
|
70
|
+
def module_method?(node)
|
71
|
+
node.receiver && node.receiver.const_type?
|
72
|
+
end
|
73
|
+
|
74
|
+
def instance_method?(feature_name)
|
75
|
+
feature_name.start_with?('#')
|
76
|
+
end
|
77
|
+
|
78
|
+
def checkable_instance_method?(node)
|
79
|
+
INSTANCE_METHODS.include?(node.method_name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def observer_method?(node)
|
83
|
+
OBSERVER_METHODS.include?(node.method_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|