inclusive-code 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 495cf47e5d18f5dad603026d5314bdfeeaeae01a045884444e2eb6316fc1ae68
4
+ data.tar.gz: 774b4de9e09abae3ec9c80373a9a6d3af1f63593df0ddd18a4922ad2deb92c46
5
+ SHA512:
6
+ metadata.gz: c6cf747c66926af9c1bc9d6c7fcdc0cf52ee3ee9b3c20c17238989a5a2983c569bea8929dfcade6cf5f60333fb604a5a6ff961e8abc7d9b842c0692e4d07e281
7
+ data.tar.gz: e850f2f2cfae0a2ece5efd6368c8d8e45a3f31544aff933b4fccd802bcbb4bce420854472dc83c609c01aeaa6b1c8fc0a43060ebb52fa9a1af34897fc40aec88
data/README.md ADDED
@@ -0,0 +1 @@
1
+ ## TODO: add usage instructions for rubocop gem
@@ -0,0 +1,4 @@
1
+ Flexport/InclusiveCode:
2
+ Description: "Warns on usage of flagged terms, suggests alternatives and allows for exceptions as configured."
3
+ Enabled: true
4
+ VersionAdded: "0.1.0"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ require_relative 'inclusive_code/flexport'
6
+ require_relative 'inclusive_code/flexport/version'
7
+ require_relative 'inclusive_code/flexport/inject'
8
+
9
+ InclusiveCode::Flexport::Inject.defaults!
10
+
11
+ require_relative 'inclusive_code/cop/inclusive_code'
@@ -0,0 +1,139 @@
1
+ module InclusiveCode
2
+ module Cop
3
+ module Flexport
4
+ # This cop encourages use of inclusive language to help programmers avoid
5
+ # using terminology that is derogatory, hurtful, or perpetuates discrimination,
6
+ # either directly or indirectly, in their code.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ #
12
+ # BLACKLIST_COUNTRIES = "blacklist_countries".freeze
13
+ #
14
+ # @example
15
+ #
16
+ # # good
17
+ #
18
+ # BLOCKLIST_COUNTRIES = "blocklist_countries".freeze
19
+ #
20
+ #
21
+ class InclusiveCode < Cop
22
+ include RangeHelp
23
+
24
+ SEVERITY = "warning".freeze
25
+
26
+ MSG = "🚫 Use of non_inclusive word: `%<non_inclusive_word>s`. Consider using these suggested alternatives: `%<suggestions>s`.".freeze
27
+
28
+ NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH = YAML.load_file(global_config_path)["flagged_terms"]
29
+
30
+ ALL_NON_INCLUSIVE_WORDS = NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH.keys
31
+
32
+ NON_INCLUSIVE_WORDS_REGEX = Regexp.new(
33
+ ALL_NON_INCLUSIVE_WORDS.join("|"),
34
+ Regexp::IGNORECASE,
35
+ )
36
+
37
+ def self.get_allowed_string(non_inclusive_word)
38
+ allowed = NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH[non_inclusive_word]["allowed"]
39
+ snake_case = allowed.map { |e| e.tr(" ", "_").underscore }
40
+ pascal_case = snake_case.map(&:camelize)
41
+ (allowed + snake_case + pascal_case).join("|")
42
+ end
43
+
44
+ ALLOWED = ALL_NON_INCLUSIVE_WORDS.collect do |word|
45
+ [
46
+ word,
47
+ get_allowed_string(word),
48
+ ]
49
+ end.to_h
50
+
51
+ ALLOWED_REGEX = Regexp.new(ALLOWED.values.reject(&:blank?).join("|"), Regexp::IGNORECASE)
52
+
53
+ def investigate(processed_source)
54
+ processed_source.lines.each_with_index do |line, line_number|
55
+ if line.match(NON_INCLUSIVE_WORDS_REGEX)
56
+ ALL_NON_INCLUSIVE_WORDS.each do |non_inclusive_word|
57
+ allowed = ALLOWED[non_inclusive_word]
58
+ if allowed.blank?
59
+ scan_regex = /(?=#{non_inclusive_word})/i
60
+ else
61
+ scan_regex = /(?=#{non_inclusive_word})(?!(#{ALLOWED[non_inclusive_word]}))/i
62
+ end
63
+ locations = line.enum_for(
64
+ :scan,
65
+ scan_regex,
66
+ ).map { Regexp.last_match&.offset(0)&.first }
67
+ next if locations.blank?
68
+ locations.each do |location|
69
+ range = source_range(
70
+ processed_source.buffer,
71
+ line_number + 1,
72
+ location,
73
+ non_inclusive_word.length,
74
+ )
75
+ add_offense(
76
+ range,
77
+ location: range,
78
+ message: create_message(non_inclusive_word),
79
+ severity: SEVERITY,
80
+ )
81
+ end
82
+ end
83
+ end
84
+ end
85
+ # Also error for non-inclusive language in file names
86
+ path = processed_source.path
87
+ if path.nil?
88
+ return
89
+ end
90
+ non_inclusive_words_match = path.match(NON_INCLUSIVE_WORDS_REGEX)
91
+ if non_inclusive_words_match && !path.match(ALLOWED_REGEX)
92
+ range = source_range(processed_source.buffer, 1, 0)
93
+ add_offense(
94
+ range,
95
+ location: range,
96
+ message: create_message(non_inclusive_words_match[0]),
97
+ severity: SEVERITY,
98
+ )
99
+ end
100
+ end
101
+
102
+ def autocorrect(arg_pair)
103
+ word_to_correct = arg_pair.source
104
+ word_to_correct_downcase = word_to_correct.downcase
105
+ return if !NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH.key?(word_to_correct_downcase)
106
+ return if NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH[word_to_correct_downcase]["suggestions"].blank?
107
+ corrected = NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH[word_to_correct_downcase]["suggestions"][0]
108
+
109
+ # Only respects case if it is capitalized or uniform (all upper or all lower)
110
+ to_upcase = word_to_correct == word_to_correct.upcase
111
+ if to_upcase
112
+ corrected = corrected.upcase
113
+ elsif word_to_correct == word_to_correct.capitalize
114
+ corrected = corrected.capitalize
115
+ end
116
+
117
+ ->(corrector) do
118
+ corrector.insert_before(arg_pair, corrected)
119
+ corrector.remove(arg_pair)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def create_message(non_inclusive_word)
126
+ format(
127
+ MSG,
128
+ non_inclusive_word: non_inclusive_word,
129
+ suggestions: NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH[non_inclusive_word]["suggestions"].join(", "),
130
+ )
131
+ end
132
+
133
+ def global_config_path
134
+ cop_config['GlobalConfigPath']
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'inclusive_code/flexport/version'
4
+
5
+ module InclusiveCode
6
+ module Flexport
7
+ class Error < StandardError; end
8
+ PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
9
+ CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
10
+ CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
11
+
12
+ private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The original code is from https://github.com/rubocop-hq/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
4
+ # See https://github.com/rubocop-hq/rubocop-rspec/blob/master/MIT-LICENSE.md
5
+ module InclusiveCode
6
+ module Flexport
7
+ # Because RuboCop doesn't yet support plugins, we have to monkey patch in a
8
+ # bit of our configuration.
9
+ module Inject
10
+ def self.defaults!
11
+ path = CONFIG_DEFAULT.to_s
12
+ hash = ConfigLoader.send(:load_yaml_configuration, path)
13
+ config = Config.new(hash, path)
14
+ puts "configuration from #{path}" if ConfigLoader.debug?
15
+ config = ConfigLoader.merge_with_default(config, path)
16
+ ConfigLoader.instance_variable_set(:@default_configuration, config)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InclusiveCode
4
+ module Flexport
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inclusive-code
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Flexport Engineering
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.70.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.70.0
41
+ description: ''
42
+ email:
43
+ - dev@flexport.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - config/default.yml
50
+ - lib/inclusive-code.rb
51
+ - lib/inclusive_code/cop/inclusive_code.rb
52
+ - lib/inclusive_code/flexport.rb
53
+ - lib/inclusive_code/flexport/inject.rb
54
+ - lib/inclusive_code/flexport/version.rb
55
+ homepage: https://github.com/flexport/rubocop-flexport
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '2.4'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.7.7
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Inclusive Language RuboCop.
79
+ test_files: []