inclusive-code 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 495cf47e5d18f5dad603026d5314bdfeeaeae01a045884444e2eb6316fc1ae68
4
- data.tar.gz: 774b4de9e09abae3ec9c80373a9a6d3af1f63593df0ddd18a4922ad2deb92c46
3
+ metadata.gz: 526c2a3af54b5c078305501a906da6955a6b38341fe8fc7ee912ef454266569f
4
+ data.tar.gz: 9b7dbe5469e6cc7e25a829ed3fc09eddd8bd6ce3a89b3cdfa04bad74ea16fa3b
5
5
  SHA512:
6
- metadata.gz: c6cf747c66926af9c1bc9d6c7fcdc0cf52ee3ee9b3c20c17238989a5a2983c569bea8929dfcade6cf5f60333fb604a5a6ff961e8abc7d9b842c0692e4d07e281
7
- data.tar.gz: e850f2f2cfae0a2ece5efd6368c8d8e45a3f31544aff933b4fccd802bcbb4bce420854472dc83c609c01aeaa6b1c8fc0a43060ebb52fa9a1af34897fc40aec88
6
+ metadata.gz: 8e94367cb358fbcecae65d671bb92982f71d41c6d519cb55dbc8eef178f9d4ae4f220cfbf5c1220281c0b1d053173b69ed61843c4fa7faf74c113b1193260bdc
7
+ data.tar.gz: e1d72adfe0ae190d5cd6a448fdc34add51be2b057cd0adb63d58655b102540ef5492f22ffdf32910ff47de81da5ad45934b56ec396b4d8b5133bde4943f335b8
data/README.md CHANGED
@@ -1 +1,33 @@
1
- ## TODO: add usage instructions for rubocop gem
1
+ ## Installation
2
+
3
+ Add this line to your application's Gemfile:
4
+
5
+ ```ruby
6
+ gem 'inclusive-code'
7
+ ```
8
+
9
+ And then execute:
10
+
11
+ $ bundle
12
+
13
+ Or install it yourself as:
14
+
15
+ $ gem install inclusive-code
16
+
17
+ ## Usage
18
+ Configure your own set of flagged terms following the structure of the `inclusive_code_flagged_terms.yml` file at the top level of this repo. We recommend storing it in `'app/constants/inclusive_code/inclusive_code_flagged_terms.yml'`.
19
+
20
+ Put this into your .rubocop.yml:
21
+
22
+ ```
23
+ require:
24
+ - inclusive-code
25
+
26
+ Flexport/InclusiveCode:
27
+ Enabled: true
28
+ GlobalConfigPath: 'app/constants/inclusive_code/inclusive_code_flagged_terms.yml' # or your path
29
+ ```
30
+
31
+ You can run the cop on your entire codebase with `rubocop --only Flexport/InclusiveCode`.
32
+
33
+ You can run the cop on a specific file with `rubocop --only Flexport/InclusiveCode file_path`.
data/config/default.yml CHANGED
@@ -2,3 +2,4 @@ Flexport/InclusiveCode:
2
2
  Description: "Warns on usage of flagged terms, suggests alternatives and allows for exceptions as configured."
3
3
  Enabled: true
4
4
  VersionAdded: "0.1.0"
5
+ GlobalConfigPath: ""
@@ -2,10 +2,10 @@
2
2
 
3
3
  require 'rubocop'
4
4
 
5
- require_relative 'inclusive_code/flexport'
6
- require_relative 'inclusive_code/flexport/version'
7
- require_relative 'inclusive_code/flexport/inject'
5
+ require_relative 'rubocop/inclusive_code'
6
+ require_relative 'rubocop/inclusive_code/version'
7
+ require_relative 'rubocop/inclusive_code/inject'
8
8
 
9
- InclusiveCode::Flexport::Inject.defaults!
9
+ RuboCop::InclusiveCode::Inject.defaults!
10
10
 
11
- require_relative 'inclusive_code/cop/inclusive_code'
11
+ require_relative 'rubocop/cop/inclusive_code'
@@ -0,0 +1,139 @@
1
+ require 'active_support/core_ext/string'
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Flexport
6
+ # This cop encourages use of inclusive language to help programmers avoid
7
+ # using terminology that is derogatory, hurtful, or perpetuates discrimination,
8
+ # either directly or indirectly, in their code.
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ #
14
+ # BLACKLIST_COUNTRIES = "blacklist_countries".freeze
15
+ #
16
+ # @example
17
+ #
18
+ # # good
19
+ #
20
+ # BLOCKLIST_COUNTRIES = "blocklist_countries".freeze
21
+ #
22
+ #
23
+ class InclusiveCode < Cop
24
+ include RangeHelp
25
+
26
+ SEVERITY = 'warning'.freeze
27
+
28
+ MSG = '🚫 Use of non_inclusive word: `%<non_inclusive_word>s`. Consider using these suggested alternatives: `%<suggestions>s`.'.freeze
29
+
30
+ def initialize(config = nil, options = nil, source_file = nil)
31
+ super(config, options)
32
+
33
+ source_file ||= YAML.load_file(cop_config['GlobalConfigPath'])
34
+ @non_inclusive_words_alternatives_hash = source_file['flagged_terms']
35
+ @all_non_inclusive_words = @non_inclusive_words_alternatives_hash.keys
36
+ @non_inclusive_words_regex = Regexp.new(
37
+ @all_non_inclusive_words.join('|'),
38
+ Regexp::IGNORECASE
39
+ )
40
+ @allowed = @all_non_inclusive_words.collect do |word|
41
+ [
42
+ word,
43
+ get_allowed_string(word)
44
+ ]
45
+ end.to_h
46
+ @allowed_regex = Regexp.new(@allowed.values.reject(&:blank?).join('|'), Regexp::IGNORECASE)
47
+ end
48
+
49
+ def investigate(processed_source)
50
+ processed_source.lines.each_with_index do |line, line_number|
51
+ next unless line.match(@non_inclusive_words_regex)
52
+
53
+ @all_non_inclusive_words.each do |non_inclusive_word|
54
+ allowed = @allowed[non_inclusive_word]
55
+ scan_regex = if allowed.blank?
56
+ /(?=#{non_inclusive_word})/i
57
+ else
58
+ /(?=#{non_inclusive_word})(?!(#{@allowed[non_inclusive_word]}))/i
59
+ end
60
+ locations = line.enum_for(
61
+ :scan,
62
+ scan_regex
63
+ ).map { Regexp.last_match&.offset(0)&.first }
64
+ next if locations.blank?
65
+
66
+ locations.each do |location|
67
+ range = source_range(
68
+ processed_source.buffer,
69
+ line_number + 1,
70
+ location,
71
+ non_inclusive_word.length
72
+ )
73
+ add_offense(
74
+ range,
75
+ location: range,
76
+ message: create_message(non_inclusive_word),
77
+ severity: SEVERITY
78
+ )
79
+ end
80
+ end
81
+ end
82
+ # Also error for non-inclusive language in file names
83
+ path = processed_source.path
84
+ return if path.nil?
85
+
86
+ non_inclusive_words_match = path.match(@non_inclusive_words_regex)
87
+ return unless non_inclusive_words_match && !path.match(@allowed_regex)
88
+
89
+ range = source_range(processed_source.buffer, 1, 0)
90
+ add_offense(
91
+ range,
92
+ location: range,
93
+ message: create_message(non_inclusive_words_match[0]),
94
+ severity: SEVERITY
95
+ )
96
+ end
97
+
98
+ def autocorrect(arg_pair)
99
+ word_to_correct = arg_pair.source
100
+ word_to_correct_downcase = word_to_correct.downcase
101
+ return unless @non_inclusive_words_alternatives_hash.key?(word_to_correct_downcase)
102
+ return if @non_inclusive_words_alternatives_hash[word_to_correct_downcase]['suggestions'].blank?
103
+
104
+ corrected = @non_inclusive_words_alternatives_hash[word_to_correct_downcase]['suggestions'][0]
105
+
106
+ # Only respects case if it is capitalized or uniform (all upper or all lower)
107
+ to_upcase = word_to_correct == word_to_correct.upcase
108
+ if to_upcase
109
+ corrected = corrected.upcase
110
+ elsif word_to_correct == word_to_correct.capitalize
111
+ corrected = corrected.capitalize
112
+ end
113
+
114
+ lambda do |corrector|
115
+ corrector.insert_before(arg_pair, corrected)
116
+ corrector.remove(arg_pair)
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def get_allowed_string(non_inclusive_word)
123
+ allowed = @non_inclusive_words_alternatives_hash[non_inclusive_word]['allowed']
124
+ snake_case = allowed.map { |e| e.tr(' ', '_').underscore }
125
+ pascal_case = snake_case.map(&:camelize)
126
+ (allowed + snake_case + pascal_case).join('|')
127
+ end
128
+
129
+ def create_message(non_inclusive_word)
130
+ format(
131
+ MSG,
132
+ non_inclusive_word: non_inclusive_word,
133
+ suggestions: @non_inclusive_words_alternatives_hash[non_inclusive_word]['suggestions'].join(', ')
134
+ )
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'inclusive_code/flexport/version'
3
+ require 'rubocop/inclusive_code/version'
4
4
 
5
- module InclusiveCode
6
- module Flexport
5
+ module RuboCop
6
+ module InclusiveCode
7
7
  class Error < StandardError; end
8
8
  PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
9
9
  CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
@@ -11,4 +11,4 @@ module InclusiveCode
11
11
 
12
12
  private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
13
13
  end
14
- end
14
+ end
@@ -2,8 +2,8 @@
2
2
 
3
3
  # The original code is from https://github.com/rubocop-hq/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
4
4
  # See https://github.com/rubocop-hq/rubocop-rspec/blob/master/MIT-LICENSE.md
5
- module InclusiveCode
6
- module Flexport
5
+ module RuboCop
6
+ module InclusiveCode
7
7
  # Because RuboCop doesn't yet support plugins, we have to monkey patch in a
8
8
  # bit of our configuration.
9
9
  module Inject
@@ -17,4 +17,4 @@ module InclusiveCode
17
17
  end
18
18
  end
19
19
  end
20
- end
20
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module InclusiveCode
5
+ VERSION = '0.1.1'
6
+ end
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inclusive-code
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Flexport Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-29 00:00:00.000000000 Z
11
+ date: 2021-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -48,10 +48,10 @@ files:
48
48
  - README.md
49
49
  - config/default.yml
50
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
51
+ - lib/rubocop/cop/inclusive_code.rb
52
+ - lib/rubocop/inclusive_code.rb
53
+ - lib/rubocop/inclusive_code/inject.rb
54
+ - lib/rubocop/inclusive_code/version.rb
55
55
  homepage: https://github.com/flexport/rubocop-flexport
56
56
  licenses:
57
57
  - MIT
@@ -1,139 +0,0 @@
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
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module InclusiveCode
4
- module Flexport
5
- VERSION = '0.1.0'
6
- end
7
- end