\d+\.\d+\.\d+(p\d+)?)/ =~ `#{which("ruby")} --version`
12 | {
13 | name: "Ruby",
14 | version: version,
15 | output_command: "'#{which("ruby")}' -wc",
16 | line_match: /#{filepath}:(\d+):/,
17 | extra_gsubs: {
18 | "Syntax OK" => "",
19 | },
20 | }
21 | end
22 |
23 | def rubocop
24 | if setting?(:fix_on_save) && !filepath.include?("/vendor/ruby/gems/")
25 | fix = " --auto-correct"
26 | end
27 | rubocop_type_regex = %r{([WC]:)( \[Corrected\])? (\w+)/(\w+)}
28 | rubocop_docs_lambda = lambda do |match|
29 | rubocop_type_regex =~ match
30 | _, type, corrected, category, check = Regexp.last_match.to_a
31 | category_down = category.downcase
32 | check_down = check.downcase
33 | url = "https://rubocop.readthedocs.io/en/latest/cops_#{category_down}/##{category_down}#{check_down}"
34 | href = "javascript:TextMate.system('open #{url}', null);"
35 | "#{type}#{corrected} #{category}/#{check}"
36 | end
37 | {
38 | name: "RuboCop",
39 | version: `'#{which("rubocop")}' --version`,
40 | output_command: "'#{which("rubocop")}' --format=emacs --display-cop-names#{fix}",
41 | line_column_match: /#{filepath}:(\d+):(\d+): /,
42 | extra_gsubs: {
43 | rubocop_type_regex => rubocop_docs_lambda,
44 | "C: " => "convention: ",
45 | "W: " => "warning: ",
46 | },
47 | }
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/Support/lib/linter.rb:
--------------------------------------------------------------------------------
1 | require "#{ENV["TM_SUPPORT_PATH"]}/lib/textmate"
2 | ENV["PATH"] += ":#{ENV["TM_BUNDLE_SUPPORT"]}/bin"
3 | $LOAD_PATH << "#{ENV["TM_BUNDLE_SUPPORT"]}/lib"
4 |
5 | module Linter
6 | module_function
7 |
8 | def tm_scope
9 | ENV["TM_SCOPE"].to_s
10 | end
11 |
12 | def tm_scopes
13 | tm_scope.split(" ")
14 | end
15 |
16 | def setting?(key)
17 | tm_scopes.include?("bundle.linter.#{key.to_s.tr("_", "-")}")
18 | end
19 |
20 | def strip_trailing_whitespace!(write: true)
21 | return if filepath.empty?
22 |
23 | end_string = "__END__\n".freeze
24 | seen_end = false
25 | whitespace_regex = /[\t ]+$/
26 | empty_string = "".freeze
27 | @stdin_lines ||= STDIN.read.lines
28 | @stdin_lines.map! do |line|
29 | if seen_end || line == end_string
30 | seen_end ||= true
31 | line
32 | else
33 | line.gsub(whitespace_regex, empty_string)
34 | end
35 | end
36 | return unless write
37 | IO.write(filepath, @stdin_lines.join)
38 | end
39 |
40 | def ensure_trailing_newline!
41 | return if filepath.empty?
42 |
43 | @stdin_lines ||= STDIN.read.lines
44 | last_char = @stdin_lines.last.to_s.chars.last
45 | newline = "\n".freeze
46 | @stdin_lines << newline if last_char != newline
47 | IO.write(filepath, @stdin_lines.join)
48 | end
49 |
50 | def lint_strip_ensure_on_save
51 | if setting?(:strip_whitespace_on_save)
52 | if setting?(:ensure_newline_on_save)
53 | strip_trailing_whitespace!(write: false)
54 | ensure_trailing_newline!
55 | else
56 | strip_trailing_whitespace!
57 | end
58 | elsif setting?(:ensure_newline_on_save)
59 | ensure_trailing_newline!
60 | end
61 |
62 | lint(manually_requested: false) if setting?(:lint_on_save)
63 | end
64 |
65 | def lint(manually_requested: true)
66 | return if filepath.empty?
67 |
68 | language_found = false
69 | language_selectors = {
70 | bash: { select: /source\.shell/ },
71 | json: { select: /source\.json/ },
72 | markdown: { select: /text\.html\.markdown/ },
73 | ruby: {
74 | select: /source\.ruby/,
75 | reject: /(source\.ruby\.embedded(\.haml)?|text\.html\.(erb|ruby))/,
76 | },
77 | }
78 | language_selectors.each do |language, match|
79 | next if (reject = match[:reject]) && tm_scope =~ reject
80 | next unless tm_scope =~ match[:select]
81 | language_found ||= true
82 |
83 | require "languages/#{language}"
84 | output(send(language))
85 | end
86 |
87 | return if language_found
88 | return unless manually_requested
89 |
90 | first_scope = tm_scopes.first
91 | puts "Error: no Linter found for #{first_scope}! Please consider submitting a pull request to https://github.com/MikeMcQuaid/Linter.tmbundle to add one."
92 | end
93 |
94 | def filepath
95 | ENV["TM_FILEPATH"].to_s
96 | end
97 |
98 | def filename
99 | return "" if filepath.empty?
100 | File.basename(filepath)
101 | end
102 |
103 | def which(name)
104 | @which_cache ||= {}
105 | @which_cache.fetch(name) do
106 | env = name.upcase.tr("-", "_")
107 | name = ENV["LINTER_#{env}"] || ENV[env] || name
108 | which = `which '#{name}'`.chomp
109 | next if which.empty?
110 | which
111 | end
112 | end
113 |
114 | def output(all_settings)
115 | all_settings = [all_settings].flatten
116 |
117 | file_link = "#{filename}"
118 |
119 | names_versions = []
120 | all_settings.each do |settings|
121 | name = settings[:name]
122 | version = settings[:version].to_s.chomp
123 | name_version =
124 | if version.empty?
125 | settings[:name]
126 | else
127 | "#{name} #{version}"
128 | end
129 | names_versions << name_version
130 | end
131 | names_versions = names_versions.join(", ")
132 | puts "Linting #{file_link} with #{names_versions}"
133 |
134 | output_something = false
135 | all_settings.each do |settings|
136 | output = settings[:output]
137 | output ||= begin
138 | output_command = settings[:output_command]
139 | unless output_command.include?(filepath)
140 | output_command += " '#{filepath}'"
141 | end
142 | env = settings[:output_command_env]
143 | if env
144 | old_env = {}
145 | env.each do |key, value|
146 | key = key.to_s
147 | old_env[key] = ENV.delete(key)
148 | ENV[key] = value
149 | end
150 | end
151 | `#{output_command} 2>&1`
152 | ensure
153 | ENV.update(old_env) if env
154 | end
155 |
156 | if (line_column_match = settings[:line_column_match])
157 | output.gsub! \
158 | line_column_match,
159 | "L\\1 "
160 | elsif (line_match = settings[:line_match])
161 | output.gsub! \
162 | line_match,
163 | "L\\1"
164 | end
165 |
166 | if (extra_gsubs = settings[:extra_gsubs])
167 | extra_gsubs.each do |pattern, replacement|
168 | if replacement.is_a?(Proc)
169 | output.gsub! pattern, &replacement
170 | else
171 | output.gsub! pattern, replacement
172 | end
173 | end
174 | end
175 |
176 | output = output.squeeze("\n").strip.chomp
177 | next if output.empty?
178 | output_something ||= true
179 | puts output
180 | end
181 |
182 | puts "(no output)" unless output_something
183 | end
184 | end
185 |
--------------------------------------------------------------------------------
/info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | contactEmailRot13
6 | zvxr@zvxrzpdhnvq.pbz
7 | contactName
8 | Mike McQuaid
9 | description
10 | Linting functionality to make it easier to write better quality code.
11 | mainMenu
12 |
13 | items
14 |
15 | 26A8FF38-100D-4E72-9ADC-1EF964095C44
16 | B6CC0FDC-0705-4435-AAFA-BC45A8E9A0E7
17 | 5983C2DD-EB3D-4282-AF06-DC0CC94DD9D0
18 | ------------------------------------
19 | 483D338D-EC9F-4777-92C0-DFCBE02BD3A0
20 |
21 |
22 | name
23 | Linter
24 | uuid
25 | 4A4ECE1D-B6CC-49BA-B8CD-B367A0F1C3AA
26 |
27 |
28 |
--------------------------------------------------------------------------------