erb_lint 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/exe/erblint +1 -1
  3. data/lib/erb_lint.rb +1 -1
  4. data/lib/erb_lint/all.rb +15 -15
  5. data/lib/erb_lint/cli.rb +18 -18
  6. data/lib/erb_lint/corrector.rb +1 -1
  7. data/lib/erb_lint/linter.rb +3 -3
  8. data/lib/erb_lint/linter_config.rb +3 -3
  9. data/lib/erb_lint/linter_registry.rb +2 -2
  10. data/lib/erb_lint/linters/allowed_script_type.rb +7 -7
  11. data/lib/erb_lint/linters/closing_erb_tag_indent.rb +2 -2
  12. data/lib/erb_lint/linters/deprecated_classes.rb +7 -7
  13. data/lib/erb_lint/linters/erb_safety.rb +2 -2
  14. data/lib/erb_lint/linters/extra_newline.rb +1 -1
  15. data/lib/erb_lint/linters/final_newline.rb +2 -2
  16. data/lib/erb_lint/linters/hard_coded_string.rb +36 -36
  17. data/lib/erb_lint/linters/no_javascript_tag_helper.rb +8 -8
  18. data/lib/erb_lint/linters/require_input_autocomplete.rb +6 -8
  19. data/lib/erb_lint/linters/require_script_nonce.rb +8 -8
  20. data/lib/erb_lint/linters/right_trim.rb +1 -1
  21. data/lib/erb_lint/linters/rubocop.rb +10 -10
  22. data/lib/erb_lint/linters/rubocop_text.rb +1 -1
  23. data/lib/erb_lint/linters/self_closing_tag.rb +5 -7
  24. data/lib/erb_lint/linters/space_around_erb_tag.rb +5 -5
  25. data/lib/erb_lint/linters/space_in_html_tag.rb +6 -6
  26. data/lib/erb_lint/linters/space_indentation.rb +1 -1
  27. data/lib/erb_lint/linters/trailing_whitespace.rb +1 -1
  28. data/lib/erb_lint/reporter.rb +2 -2
  29. data/lib/erb_lint/reporters/compact_reporter.rb +1 -1
  30. data/lib/erb_lint/reporters/multiline_reporter.rb +1 -1
  31. data/lib/erb_lint/runner.rb +1 -1
  32. data/lib/erb_lint/runner_config.rb +7 -7
  33. data/lib/erb_lint/runner_config_resolver.rb +4 -4
  34. data/lib/erb_lint/stats.rb +6 -6
  35. data/lib/erb_lint/utils/block_map.rb +2 -2
  36. data/lib/erb_lint/utils/offset_corrector.rb +1 -1
  37. data/lib/erb_lint/utils/ruby_to_erb.rb +5 -5
  38. data/lib/erb_lint/utils/severity_levels.rb +2 -2
  39. data/lib/erb_lint/version.rb +1 -1
  40. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c54cf59770c0719d8cae0376d27a685ba2159d0e8884c00a472b0217d1ca2e6
4
- data.tar.gz: c644e23a805c04e02daf4218f8548a167a90cc8d0b2bf0fd73acbbdbbaec237c
3
+ metadata.gz: b98507494f8af2a33e0e6fcbf28c48a2d91c78f42273a3cc6c1206578f3a55b0
4
+ data.tar.gz: c090a633a9d041a6d4d71bc292b1525b3a74dccd61311e0224bc1cbb0d8fea33
5
5
  SHA512:
6
- metadata.gz: '07718e5eed2bfb8246a529615c3950645c0d51e376475c6cea7eb60bedfc17af42c4f24f3aba7e8184e842087027b3c0e033afb6c1d01eceb5b84e7c0da8bb74'
7
- data.tar.gz: 6bda3b3ad2b03a0812a6bb796c0f6f2d03545fcdc44a6130dc7e3b2135d523e8decb395e3fd7599d9121045c840585de3c4ced6c9c8fb642d1865d263e978dad
6
+ metadata.gz: b2ac77d88b7be36010d69c072e3ee72c370edefa1fc3b68135593b5b494da8b0f9eac79e9b5408d10155a007d94948ae6a4b3b279de2541be66f0576abc964a8
7
+ data.tar.gz: f0be6a75c5060b49a8b7c5e5a5d19adb3222fb580a11c2e2e488e5b83ada00db0c4daee25e99ba5538057c749f5937b80d25a96bda1ed967e6d1baeddf787965
data/exe/erblint CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  $LOAD_PATH.unshift("#{__dir__}/../lib")
5
5
 
6
- require 'erb_lint/cli'
6
+ require "erb_lint/cli"
7
7
 
8
8
  cli = ERBLint::CLI.new
9
9
  exit(cli.run)
data/lib/erb_lint.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'erb_lint/version'
3
+ require "erb_lint/version"
data/lib/erb_lint/all.rb CHANGED
@@ -1,26 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rubocop'
3
+ require "rubocop"
4
4
 
5
- require 'erb_lint'
6
- require 'erb_lint/corrector'
7
- require 'erb_lint/file_loader'
8
- require 'erb_lint/linter_config'
9
- require 'erb_lint/linter_registry'
10
- require 'erb_lint/linter'
11
- require 'erb_lint/offense'
12
- require 'erb_lint/processed_source'
13
- require 'erb_lint/runner_config'
14
- require 'erb_lint/runner'
15
- require 'erb_lint/stats'
16
- require 'erb_lint/reporter'
5
+ require "erb_lint"
6
+ require "erb_lint/corrector"
7
+ require "erb_lint/file_loader"
8
+ require "erb_lint/linter_config"
9
+ require "erb_lint/linter_registry"
10
+ require "erb_lint/linter"
11
+ require "erb_lint/offense"
12
+ require "erb_lint/processed_source"
13
+ require "erb_lint/runner_config"
14
+ require "erb_lint/runner"
15
+ require "erb_lint/stats"
16
+ require "erb_lint/reporter"
17
17
 
18
18
  # Load linters
19
- Dir[File.expand_path('linters/**/*.rb', __dir__)].each do |file|
19
+ Dir[File.expand_path("linters/**/*.rb", __dir__)].each do |file|
20
20
  require file
21
21
  end
22
22
 
23
23
  # Load reporters
24
- Dir[File.expand_path('reporters/**/*.rb', __dir__)].each do |file|
24
+ Dir[File.expand_path("reporters/**/*.rb", __dir__)].each do |file|
25
25
  require file
26
26
  end
data/lib/erb_lint/cli.rb CHANGED
@@ -1,19 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'erb_lint/all'
4
- require 'active_support'
5
- require 'active_support/inflector'
6
- require 'optparse'
7
- require 'psych'
8
- require 'yaml'
9
- require 'rainbow'
10
- require 'erb_lint/utils/severity_levels'
3
+ require "erb_lint/all"
4
+ require "active_support"
5
+ require "active_support/inflector"
6
+ require "optparse"
7
+ require "psych"
8
+ require "yaml"
9
+ require "rainbow"
10
+ require "erb_lint/utils/severity_levels"
11
11
 
12
12
  module ERBLint
13
13
  class CLI
14
14
  include Utils::SeverityLevels
15
15
 
16
- DEFAULT_CONFIG_FILENAME = '.erb-lint.yml'
16
+ DEFAULT_CONFIG_FILENAME = ".erb-lint.yml"
17
17
  DEFAULT_LINT_ALL_GLOB = "**/*.html{+*,}.erb"
18
18
 
19
19
  class ExitWithFailure < RuntimeError; end
@@ -43,7 +43,7 @@ module ERBLint
43
43
  ensure_files_exist(lint_files)
44
44
 
45
45
  if enabled_linter_classes.empty?
46
- failure!('no linter available with current configuration')
46
+ failure!("no linter available with current configuration")
47
47
  end
48
48
 
49
49
  @options[:format] ||= :multiline
@@ -145,7 +145,7 @@ module ERBLint
145
145
 
146
146
  def correct(processed_source, offenses)
147
147
  corrector = ERBLint::Corrector.new(processed_source, offenses)
148
- failure!(corrector.diagnostics.join(', ')) if corrector.diagnostics.any?
148
+ failure!(corrector.diagnostics.join(", ")) if corrector.diagnostics.any?
149
149
  corrector
150
150
  end
151
151
 
@@ -183,7 +183,7 @@ module ERBLint
183
183
  else
184
184
  @files
185
185
  .map { |f| Dir.exist?(f) ? Dir[File.join(f, glob)] : f }
186
- .map { |f| f.include?('*') ? Dir[f] : f }
186
+ .map { |f| f.include?("*") ? Dir[f] : f }
187
187
  .flatten
188
188
  .map { |f| File.expand_path(f, Dir.pwd) }
189
189
  .select { |filename| !excluded?(filename) }
@@ -240,14 +240,14 @@ module ERBLint
240
240
  end
241
241
 
242
242
  def relative_filename(filename)
243
- filename.sub("#{File.expand_path('.', Dir.pwd)}/", '')
243
+ filename.sub("#{File.expand_path(".", Dir.pwd)}/", "")
244
244
  end
245
245
 
246
246
  def runner_config_override
247
247
  RunnerConfig.new(
248
248
  linters: {}.tap do |linters|
249
249
  ERBLint::LinterRegistry.linters.map do |klass|
250
- linters[klass.simple_name] = { 'enabled' => enabled_linter_classes.include?(klass) }
250
+ linters[klass.simple_name] = { "enabled" => enabled_linter_classes.include?(klass) }
251
251
  end
252
252
  end
253
253
  )
@@ -283,10 +283,10 @@ module ERBLint
283
283
  end
284
284
 
285
285
  opts.on("--enable-linters LINTER[,LINTER,...]", Array,
286
- "Only use specified linter", "Known linters are: #{known_linter_names.join(', ')}") do |linters|
286
+ "Only use specified linter", "Known linters are: #{known_linter_names.join(", ")}") do |linters|
287
287
  linters.each do |linter|
288
288
  unless known_linter_names.include?(linter)
289
- failure!("#{linter}: not a valid linter name (#{known_linter_names.join(', ')})")
289
+ failure!("#{linter}: not a valid linter name (#{known_linter_names.join(", ")})")
290
290
  end
291
291
  end
292
292
  @options[:enabled_linters] = linters
@@ -296,7 +296,7 @@ module ERBLint
296
296
  parsed_severity = SEVERITY_CODE_TABLE[level.upcase.to_sym] || (SEVERITY_NAMES & [level.downcase]).first
297
297
 
298
298
  if parsed_severity.nil?
299
- failure!("#{level}: not a valid failure level (#{SEVERITY_NAMES.join(', ')})")
299
+ failure!("#{level}: not a valid failure level (#{SEVERITY_NAMES.join(", ")})")
300
300
  end
301
301
  @options[:fail_level] = severity_level_for_name(parsed_severity)
302
302
  end
@@ -325,7 +325,7 @@ module ERBLint
325
325
 
326
326
  def format_options_help
327
327
  "Report offenses in the given format: "\
328
- "(#{Reporter.available_formats.join(', ')}) (default: multiline)"
328
+ "(#{Reporter.available_formats.join(", ")}) (default: multiline)"
329
329
  end
330
330
 
331
331
  def invalid_format_error_message(given_format)
@@ -21,7 +21,7 @@ module ERBLint
21
21
  end
22
22
 
23
23
  if ::RuboCop::Version::STRING.to_f >= 0.87
24
- require 'rubocop/cop/legacy/corrector'
24
+ require "rubocop/cop/legacy/corrector"
25
25
  BASE = ::RuboCop::Cop::Legacy::Corrector
26
26
 
27
27
  def diagnostics
@@ -15,9 +15,9 @@ module ERBLint
15
15
  # `ERBLint::Linters::Compass::Bar.simple_name` #=> "Compass::Bar"
16
16
  def inherited(linter)
17
17
  super
18
- linter.simple_name = if linter.name.start_with?('ERBLint::Linters::')
19
- name_parts = linter.name.split('::')
20
- name_parts[2..-1].join('::')
18
+ linter.simple_name = if linter.name.start_with?("ERBLint::Linters::")
19
+ name_parts = linter.name.split("::")
20
+ name_parts[2..-1].join("::")
21
21
  else
22
22
  linter.name
23
23
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support'
4
- require 'smart_properties'
3
+ require "active_support"
4
+ require "smart_properties"
5
5
 
6
6
  module ERBLint
7
7
  class LinterConfig
@@ -27,7 +27,7 @@ module ERBLint
27
27
  allowed_keys = self.class.properties.keys.map(&:to_s)
28
28
  given_keys = config.keys
29
29
  if (extra_keys = given_keys - allowed_keys).any?
30
- raise Error, "Given key is not allowed: #{extra_keys.join(', ')}"
30
+ raise Error, "Given key is not allowed: #{extra_keys.join(", ")}"
31
31
  end
32
32
  super(config)
33
33
  rescue SmartProperties::InitializationError => e
@@ -3,7 +3,7 @@
3
3
  module ERBLint
4
4
  # Stores all linters available to the application.
5
5
  module LinterRegistry
6
- CUSTOM_LINTERS_DIR = '.erb-linters'
6
+ CUSTOM_LINTERS_DIR = ".erb-linters"
7
7
  @loaded_linters = []
8
8
 
9
9
  class << self
@@ -27,7 +27,7 @@ module ERBLint
27
27
  end
28
28
 
29
29
  def load_custom_linters(directory = CUSTOM_LINTERS_DIR)
30
- ruby_files = Dir.glob(File.expand_path(File.join(directory, '**', '*.rb')))
30
+ ruby_files = Dir.glob(File.expand_path(File.join(directory, "**", "*.rb")))
31
31
  ruby_files.each { |file| require file }
32
32
  end
33
33
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'better_html/tree/tag'
3
+ require "better_html"
4
+ require "better_html/tree/tag"
5
5
 
6
6
  module ERBLint
7
7
  module Linters
@@ -13,7 +13,7 @@ module ERBLint
13
13
 
14
14
  class ConfigSchema < LinterConfig
15
15
  property :allowed_types, accepts: array_of?(String),
16
- default: -> { ['text/javascript'] }
16
+ default: -> { ["text/javascript"] }
17
17
  property :allow_blank, accepts: [true, false], default: true, reader: :allow_blank?
18
18
  property :disallow_inline_scripts, accepts: [true, false], default: false, reader: :disallow_inline_scripts?
19
19
  end
@@ -24,7 +24,7 @@ module ERBLint
24
24
  parser.nodes_with_type(:tag).each do |tag_node|
25
25
  tag = BetterHtml::Tree::Tag.from_node(tag_node)
26
26
  next if tag.closing?
27
- next unless tag.name == 'script'
27
+ next unless tag.name == "script"
28
28
 
29
29
  if @config.disallow_inline_scripts?
30
30
  name_node = tag_node.to_a[1]
@@ -36,7 +36,7 @@ module ERBLint
36
36
  next
37
37
  end
38
38
 
39
- type_attribute = tag.attributes['type']
39
+ type_attribute = tag.attributes["type"]
40
40
  type_present = type_attribute.present? && type_attribute.value_node.present?
41
41
 
42
42
  if !type_present && !@config.allow_blank?
@@ -50,8 +50,8 @@ module ERBLint
50
50
  add_offense(
51
51
  type_attribute.loc,
52
52
  "Avoid using #{type_attribute.value.inspect} as type for `<script>` tag. "\
53
- "Must be one of: #{@config.allowed_types.join(', ')}"\
54
- "#{' (or no type attribute)' if @config.allow_blank?}."
53
+ "Must be one of: #{@config.allowed_types.join(", ")}"\
54
+ "#{" (or no type attribute)" if @config.allow_blank?}."
55
55
  )
56
56
  end
57
57
  end
@@ -25,7 +25,7 @@ module ERBLint
25
25
  add_offense(
26
26
  code_node.loc.end.adjust(begin_pos: -end_spaces.size),
27
27
  "Remove newline before `%>` to match start of tag.",
28
- ' '
28
+ " "
29
29
  )
30
30
  elsif start_with_newline && !end_with_newline
31
31
  add_offense(
@@ -39,7 +39,7 @@ module ERBLint
39
39
  add_offense(
40
40
  code_node.loc.end.adjust(begin_pos: -current_indent.size),
41
41
  "Indent `%>` on column #{erb_node.loc.column} to match start of tag.",
42
- ' ' * erb_node.loc.column
42
+ " " * erb_node.loc.column
43
43
  )
44
44
  end
45
45
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'better_html/parser'
3
+ require "better_html"
4
+ require "better_html/parser"
5
5
 
6
6
  module ERBLint
7
7
  module Linters
@@ -11,7 +11,7 @@ module ERBLint
11
11
 
12
12
  class RuleSet
13
13
  include SmartProperties
14
- property :suggestion, accepts: String, default: ''
14
+ property :suggestion, accepts: String, default: ""
15
15
  property :deprecated, accepts: LinterConfig.array_of?(String), default: -> { [] }
16
16
  end
17
17
 
@@ -57,9 +57,9 @@ module ERBLint
57
57
  def class_name_with_loc(processed_source)
58
58
  Enumerator.new do |yielder|
59
59
  tags(processed_source).each do |tag|
60
- class_value = tag.attributes['class']&.value
60
+ class_value = tag.attributes["class"]&.value
61
61
  next unless class_value
62
- class_value.split(' ').each do |class_name|
62
+ class_value.split(" ").each do |class_name|
63
63
  yielder.yield(class_name, tag.loc)
64
64
  end
65
65
  end
@@ -69,7 +69,7 @@ module ERBLint
69
69
  def text_tags_content(processed_source)
70
70
  Enumerator.new do |yielder|
71
71
  script_tags(processed_source)
72
- .select { |tag| tag.attributes['type']&.value == 'text/html' }
72
+ .select { |tag| tag.attributes["type"]&.value == "text/html" }
73
73
  .each do |tag|
74
74
  index = processed_source.ast.to_a.find_index(tag.node)
75
75
  next_node = processed_source.ast.to_a[index + 1]
@@ -80,7 +80,7 @@ module ERBLint
80
80
  end
81
81
 
82
82
  def script_tags(processed_source)
83
- tags(processed_source).select { |tag| tag.name == 'script' }
83
+ tags(processed_source).select { |tag| tag.name == "script" }
84
84
  end
85
85
 
86
86
  def tags(processed_source)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'better_html/test_helper/safe_erb_tester'
3
+ require "better_html"
4
+ require "better_html/test_helper/safe_erb_tester"
5
5
 
6
6
  module ERBLint
7
7
  module Linters
@@ -22,7 +22,7 @@ module ERBLint
22
22
 
23
23
  def autocorrect(_processed_source, offense)
24
24
  lambda do |corrector|
25
- corrector.replace(offense.source_range, '')
25
+ corrector.replace(offense.source_range, "")
26
26
  end
27
27
  end
28
28
  end
@@ -28,7 +28,7 @@ module ERBLint
28
28
  if final_newline.empty?
29
29
  add_offense(
30
30
  processed_source.to_source_range(file_content.size...file_content.size),
31
- 'Missing a trailing newline at the end of the file.',
31
+ "Missing a trailing newline at the end of the file.",
32
32
  :insert
33
33
  )
34
34
  else
@@ -36,7 +36,7 @@ module ERBLint
36
36
  processed_source.to_source_range(
37
37
  (file_content.size - final_newline.size + 1)...file_content.size
38
38
  ),
39
- 'Remove multiple trailing newline at the end of the file.',
39
+ "Remove multiple trailing newline at the end of the file.",
40
40
  :remove
41
41
  )
42
42
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "set"
3
- require 'better_html/tree/tag'
4
- require 'active_support/core_ext/string/inflections'
3
+ require "better_html/tree/tag"
4
+ require "active_support/core_ext/string/inflections"
5
5
 
6
6
  module ERBLint
7
7
  module Linters
@@ -13,37 +13,37 @@ module ERBLint
13
13
  MissingCorrector = Class.new(StandardError)
14
14
  MissingI18nLoadPath = Class.new(StandardError)
15
15
 
16
- ALLOWED_CORRECTORS = %w(
17
- I18nCorrector
18
- RuboCop::Corrector::I18n::HardCodedString
19
- )
20
-
21
- NON_TEXT_TAGS = Set.new(%w(script style xmp iframe noembed noframes listing))
22
- BLACK_LISTED_TEXT = Set.new(%w(
23
- &nbsp;
24
- &amp;
25
- &lt;
26
- &gt;
27
- &quot;
28
- &copy;
29
- &reg;
30
- &trade;
31
- &hellip;
32
- &mdash;
33
- &bull;
34
- &ldquo;
35
- &rdquo;
36
- &lsquo;
37
- &rsquo;
38
- &larr;
39
- &rarr;
40
- &darr;
41
- &uarr;
42
- ))
16
+ ALLOWED_CORRECTORS = ["I18nCorrector", "RuboCop::Corrector::I18n::HardCodedString"]
17
+
18
+ NON_TEXT_TAGS = Set.new(["script", "style", "xmp", "iframe", "noembed", "noframes", "listing"])
19
+ TEXT_NOT_ALLOWED = Set.new([
20
+ "&nbsp;",
21
+ "&amp;",
22
+ "&lt;",
23
+ "&gt;",
24
+ "&quot;",
25
+ "&copy;",
26
+ "&reg;",
27
+ "&trade;",
28
+ "&hellip;",
29
+ "&mdash;",
30
+ "&bull;",
31
+ "&ldquo;",
32
+ "&rdquo;",
33
+ "&lsquo;",
34
+ "&rsquo;",
35
+ "&larr;",
36
+ "&rarr;",
37
+ "&darr;",
38
+ "&uarr;",
39
+ "&ensp;",
40
+ "&emsp;",
41
+ "&thinsp;",
42
+ ])
43
43
 
44
44
  class ConfigSchema < LinterConfig
45
45
  property :corrector, accepts: Hash, required: false, default: -> { {} }
46
- property :i18n_load_path, accepts: String, required: false, default: ''
46
+ property :i18n_load_path, accepts: String, required: false, default: ""
47
47
  end
48
48
  self.config_schema = ConfigSchema
49
49
 
@@ -85,7 +85,7 @@ module ERBLint
85
85
  return unless string.strip.length > 1
86
86
  node = ::RuboCop::AST::StrNode.new(:str, [string])
87
87
  corrector = klass.new(node, processed_source.filename, corrector_i18n_load_path, offense.source_range)
88
- corrector.autocorrect(tag_start: '<%= ', tag_end: ' %>')
88
+ corrector.autocorrect(tag_start: "<%= ", tag_end: " %>")
89
89
  rescue MissingCorrector, MissingI18nLoadPath
90
90
  nil
91
91
  end
@@ -93,20 +93,20 @@ module ERBLint
93
93
  private
94
94
 
95
95
  def check_string?(str)
96
- string = str.gsub(/\s*/, '')
97
- string.length > 1 && !BLACK_LISTED_TEXT.include?(string)
96
+ string = str.gsub(/\s*/, "")
97
+ string.length > 1 && !TEXT_NOT_ALLOWED.include?(string)
98
98
  end
99
99
 
100
100
  def load_corrector
101
- corrector_name = @config['corrector'].fetch('name') { raise MissingCorrector }
101
+ corrector_name = @config["corrector"].fetch("name") { raise MissingCorrector }
102
102
  raise ForbiddenCorrector unless ALLOWED_CORRECTORS.include?(corrector_name)
103
- require @config['corrector'].fetch('path') { raise MissingCorrector }
103
+ require @config["corrector"].fetch("path") { raise MissingCorrector }
104
104
 
105
105
  corrector_name.safe_constantize
106
106
  end
107
107
 
108
108
  def corrector_i18n_load_path
109
- @config['corrector'].fetch('i18n_load_path') { raise MissingI18nLoadPath }
109
+ @config["corrector"].fetch("i18n_load_path") { raise MissingI18nLoadPath }
110
110
  end
111
111
 
112
112
  def non_text_tag?(processed_source, text_node)
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'better_html/ast/node'
5
- require 'better_html/test_helper/ruby_node'
6
- require 'erb_lint/utils/block_map'
7
- require 'erb_lint/utils/ruby_to_erb'
3
+ require "better_html"
4
+ require "better_html/ast/node"
5
+ require "better_html/test_helper/ruby_node"
6
+ require "erb_lint/utils/block_map"
7
+ require "erb_lint/utils/ruby_to_erb"
8
8
 
9
9
  module ERBLint
10
10
  module Linters
@@ -21,7 +21,7 @@ module ERBLint
21
21
  parser.ast.descendants(:erb).each do |erb_node|
22
22
  indicator_node, _, code_node, _ = *erb_node
23
23
  indicator = indicator_node&.loc&.source
24
- next if indicator == '#'
24
+ next if indicator == "#"
25
25
  source = code_node.loc.source
26
26
 
27
27
  ruby_node =
@@ -63,10 +63,10 @@ module ERBLint
63
63
  return unless (0..2).cover?(argument_nodes.size)
64
64
 
65
65
  script_content = unless argument_nodes.first&.type?(:hash)
66
- Utils::RubyToERB.ruby_to_erb(argument_nodes.first, '==')
66
+ Utils::RubyToERB.ruby_to_erb(argument_nodes.first, "==")
67
67
  end
68
68
  arguments = if argument_nodes.last&.type?(:hash)
69
- ' ' + Utils::RubyToERB.html_options_to_tag_attributes(argument_nodes.last)
69
+ " " + Utils::RubyToERB.html_options_to_tag_attributes(argument_nodes.last)
70
70
  end
71
71
 
72
72
  return if end_node && script_content
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'better_html/tree/tag'
3
+ require "better_html"
4
+ require "better_html/tree/tag"
5
5
 
6
6
  module ERBLint
7
7
  module Linters
@@ -13,7 +13,6 @@ module ERBLint
13
13
  "date",
14
14
  "datetime-local",
15
15
  "email",
16
- "hidden",
17
16
  "month",
18
17
  "number",
19
18
  "password",
@@ -33,7 +32,6 @@ module ERBLint
33
32
  :text_field_tag,
34
33
  :utf8_enforcer_tag,
35
34
  :month_field_tag,
36
- :hidden_field_tag,
37
35
  :number_field_tag,
38
36
  :password_field_tag,
39
37
  :search_field_tag,
@@ -56,8 +54,8 @@ module ERBLint
56
54
  parser.nodes_with_type(:tag).each do |tag_node|
57
55
  tag = BetterHtml::Tree::Tag.from_node(tag_node)
58
56
 
59
- autocomplete_attribute = tag.attributes['autocomplete']
60
- type_attribute = tag.attributes['type']
57
+ autocomplete_attribute = tag.attributes["autocomplete"]
58
+ type_attribute = tag.attributes["type"]
61
59
 
62
60
  next if !html_input_tag?(tag) || autocomplete_present?(autocomplete_attribute)
63
61
  next unless html_type_requires_autocomplete_attribute?(type_attribute)
@@ -76,7 +74,7 @@ module ERBLint
76
74
  end
77
75
 
78
76
  def html_input_tag?(tag)
79
- !tag.closing? && tag.name == 'input'
77
+ !tag.closing? && tag.name == "input"
80
78
  end
81
79
 
82
80
  def html_type_requires_autocomplete_attribute?(type_attribute)
@@ -110,7 +108,7 @@ module ERBLint
110
108
  end
111
109
 
112
110
  def code_comment?(indicator_node)
113
- indicator_node&.loc&.source == '#'
111
+ indicator_node&.loc&.source == "#"
114
112
  end
115
113
 
116
114
  def extract_ruby_node(source)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'better_html/tree/tag'
3
+ require "better_html"
4
+ require "better_html/tree/tag"
5
5
 
6
6
  module ERBLint
7
7
  module Linters
@@ -22,7 +22,7 @@ module ERBLint
22
22
  def find_html_script_tags(parser)
23
23
  parser.nodes_with_type(:tag).each do |tag_node|
24
24
  tag = BetterHtml::Tree::Tag.from_node(tag_node)
25
- nonce_attribute = tag.attributes['nonce']
25
+ nonce_attribute = tag.attributes["nonce"]
26
26
 
27
27
  next if !html_javascript_tag?(tag) || nonce_present?(nonce_attribute)
28
28
 
@@ -40,16 +40,16 @@ module ERBLint
40
40
 
41
41
  def html_javascript_tag?(tag)
42
42
  !tag.closing? &&
43
- (tag.name == 'script' && !html_javascript_type_attribute?(tag))
43
+ (tag.name == "script" && !html_javascript_type_attribute?(tag))
44
44
  end
45
45
 
46
46
  def html_javascript_type_attribute?(tag)
47
- type_attribute = tag.attributes['type']
47
+ type_attribute = tag.attributes["type"]
48
48
 
49
49
  type_attribute &&
50
50
  type_attribute.value_node.present? &&
51
- type_attribute.value_node.to_a[1] != 'text/javascript' &&
52
- type_attribute.value_node.to_a[1] != 'application/javascript'
51
+ type_attribute.value_node.to_a[1] != "text/javascript" &&
52
+ type_attribute.value_node.to_a[1] != "application/javascript"
53
53
  end
54
54
 
55
55
  def find_rails_helper_script_tags(parser)
@@ -79,7 +79,7 @@ module ERBLint
79
79
  end
80
80
 
81
81
  def code_comment?(indicator_node)
82
- indicator_node&.loc&.source == '#'
82
+ indicator_node&.loc&.source == "#"
83
83
  end
84
84
 
85
85
  def extract_ruby_node(source)
@@ -8,7 +8,7 @@ module ERBLint
8
8
  include LinterRegistry
9
9
 
10
10
  class ConfigSchema < LinterConfig
11
- property :enforced_style, accepts: ['-', '='], default: '-'
11
+ property :enforced_style, accepts: ["-", "="], default: "-"
12
12
  end
13
13
  self.config_schema = ConfigSchema
14
14
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'tempfile'
5
- require 'erb_lint/utils/offset_corrector'
3
+ require "better_html"
4
+ require "tempfile"
5
+ require "erb_lint/utils/offset_corrector"
6
6
 
7
7
  module ERBLint
8
8
  module Linters
@@ -25,7 +25,7 @@ module ERBLint
25
25
  super
26
26
  @only_cops = @config.only
27
27
  custom_config = config_from_hash(@config.rubocop_config)
28
- @rubocop_config = ::RuboCop::ConfigLoader.merge_with_default(custom_config, '')
28
+ @rubocop_config = ::RuboCop::ConfigLoader.merge_with_default(custom_config, "")
29
29
  end
30
30
 
31
31
  def run(processed_source)
@@ -68,13 +68,13 @@ module ERBLint
68
68
 
69
69
  def inspect_content(processed_source, erb_node)
70
70
  indicator, _, code_node, = *erb_node
71
- return if indicator&.children&.first == '#'
71
+ return if indicator&.children&.first == "#"
72
72
 
73
73
  original_source = code_node.loc.source
74
- trimmed_source = original_source.sub(BLOCK_EXPR, '').sub(SUFFIX_EXPR, '')
74
+ trimmed_source = original_source.sub(BLOCK_EXPR, "").sub(SUFFIX_EXPR, "")
75
75
  alignment_column = code_node.loc.column
76
76
  offset = code_node.loc.begin_pos - alignment_column
77
- aligned_source = "#{' ' * alignment_column}#{trimmed_source}"
77
+ aligned_source = "#{" " * alignment_column}#{trimmed_source}"
78
78
 
79
79
  source = rubocop_processed_source(aligned_source, processed_source.filename)
80
80
  return unless source.valid_syntax?
@@ -156,10 +156,10 @@ module ERBLint
156
156
  end
157
157
 
158
158
  def config_from_hash(hash)
159
- inherit_from = hash&.delete('inherit_from')
159
+ inherit_from = hash&.delete("inherit_from")
160
160
  resolve_inheritance(hash, inherit_from)
161
161
 
162
- tempfile_from('.erblint-rubocop', hash.to_yaml) do |tempfile|
162
+ tempfile_from(".erblint-rubocop", hash.to_yaml) do |tempfile|
163
163
  ::RuboCop::ConfigLoader.load_file(tempfile.path)
164
164
  end
165
165
  end
@@ -174,7 +174,7 @@ module ERBLint
174
174
  end
175
175
 
176
176
  def base_configs(inherit_from)
177
- regex = URI::DEFAULT_PARSER.make_regexp(%w(http https))
177
+ regex = URI::DEFAULT_PARSER.make_regexp(["http", "https"])
178
178
  configs = Array(inherit_from).compact.map do |base_name|
179
179
  if base_name =~ /\A#{regex}\z/
180
180
  ::RuboCop::ConfigLoader.load_file(::RuboCop::RemoteConfig.new(base_name, Dir.pwd))
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'rubocop'
3
+ require_relative "rubocop"
4
4
 
5
5
  module ERBLint
6
6
  module Linters
@@ -11,10 +11,8 @@ module ERBLint
11
11
  end
12
12
  self.config_schema = ConfigSchema
13
13
 
14
- SELF_CLOSING_TAGS = %w(
15
- area base br col command embed hr input keygen
16
- link menuitem meta param source track wbr img
17
- )
14
+ SELF_CLOSING_TAGS = ["area", "base", "br", "col", "command", "embed", "hr", "input", "keygen", "link",
15
+ "menuitem", "meta", "param", "source", "track", "wbr", "img",]
18
16
 
19
17
  def run(processed_source)
20
18
  processed_source.ast.descendants(:tag).each do |tag_node|
@@ -26,7 +24,7 @@ module ERBLint
26
24
  add_offense(
27
25
  start_solidus.loc,
28
26
  "Tag `#{tag.name}` is a void element, it must not start with `</`.",
29
- ''
27
+ ""
30
28
  )
31
29
  end
32
30
 
@@ -34,7 +32,7 @@ module ERBLint
34
32
  add_offense(
35
33
  tag_node.loc.end.offset(-1),
36
34
  "Tag `#{tag.name}` is self-closing, it must end with `/>`.",
37
- '/'
35
+ "/"
38
36
  )
39
37
  end
40
38
 
@@ -43,7 +41,7 @@ module ERBLint
43
41
  add_offense(
44
42
  end_solidus.loc,
45
43
  "Tag `#{tag.name}` is a void element, it must end with `>` and not `/>`.",
46
- ''
44
+ ""
47
45
  )
48
46
  end
49
47
  end
@@ -15,7 +15,7 @@ module ERBLint
15
15
  processed_source.ast.descendants(:erb).each do |erb_node|
16
16
  indicator_node, ltrim, code_node, rtrim = *erb_node
17
17
  indicator = indicator_node&.loc&.source
18
- next if indicator == '#' || indicator == '%'
18
+ next if indicator == "#" || indicator == "%"
19
19
  code = code_node.children.first
20
20
 
21
21
  start_spaces = code.match(START_SPACES)&.captures&.first || ""
@@ -23,8 +23,8 @@ module ERBLint
23
23
  add_offense(
24
24
  code_node.loc.resize(start_spaces.size),
25
25
  "Use 1 space after `<%#{indicator}#{ltrim&.loc&.source}` "\
26
- "instead of #{start_spaces.size} space#{'s' if start_spaces.size > 1}.",
27
- ' '
26
+ "instead of #{start_spaces.size} space#{"s" if start_spaces.size > 1}.",
27
+ " "
28
28
  )
29
29
  elsif start_spaces.count("\n") > 1
30
30
  lines = start_spaces.split("\n", -1)
@@ -41,8 +41,8 @@ module ERBLint
41
41
  add_offense(
42
42
  code_node.loc.end.adjust(begin_pos: -end_spaces.size),
43
43
  "Use 1 space before `#{rtrim&.loc&.source}%>` "\
44
- "instead of #{end_spaces.size} space#{'s' if start_spaces.size > 1}.",
45
- ' '
44
+ "instead of #{end_spaces.size} space#{"s" if start_spaces.size > 1}.",
45
+ " "
46
46
  )
47
47
  elsif end_spaces.count("\n") > 1
48
48
  lines = end_spaces.split("\n", -1)
@@ -50,7 +50,7 @@ module ERBLint
50
50
  add_offense(
51
51
  processed_source.to_source_range(range),
52
52
  "Extra space detected where there should be no space.",
53
- ''
53
+ ""
54
54
  )
55
55
  end
56
56
 
@@ -60,24 +60,24 @@ module ERBLint
60
60
 
61
61
  def single_space(processed_source, range, accept_newline: false)
62
62
  chars = processed_source.file_content[range]
63
- return if chars == ' '
63
+ return if chars == " "
64
64
 
65
65
  newlines = chars.include?("\n")
66
- expected = newlines && accept_newline ? "\n#{chars.split("\n", -1).last}" : ' '
66
+ expected = newlines && accept_newline ? "\n#{chars.split("\n", -1).last}" : " "
67
67
  non_space = chars.match(/([^[[:space:]]])/m)
68
68
 
69
69
  if non_space && !non_space.captures.empty?
70
70
  add_offense(
71
71
  processed_source.to_source_range(range),
72
72
  "Non-whitespace character(s) detected: "\
73
- "#{non_space.captures.map(&:inspect).join(', ')}.",
73
+ "#{non_space.captures.map(&:inspect).join(", ")}.",
74
74
  expected
75
75
  )
76
76
  elsif newlines && accept_newline
77
77
  if expected != chars
78
78
  add_offense(
79
79
  processed_source.to_source_range(range),
80
- "#{chars.empty? ? 'No' : 'Extra'} space detected where there should be "\
80
+ "#{chars.empty? ? "No" : "Extra"} space detected where there should be "\
81
81
  "a single space or a single line break.",
82
82
  expected
83
83
  )
@@ -85,7 +85,7 @@ module ERBLint
85
85
  else
86
86
  add_offense(
87
87
  processed_source.to_source_range(range),
88
- "#{chars.empty? ? 'No' : 'Extra'} space detected where there should be a single space.",
88
+ "#{chars.empty? ? "No" : "Extra"} space detected where there should be a single space.",
89
89
  expected
90
90
  )
91
91
  end
@@ -23,7 +23,7 @@ module ERBLint
23
23
  add_offense(
24
24
  processed_source.to_source_range(document_pos...(document_pos + spaces.length)),
25
25
  "Indent with spaces instead of tabs.",
26
- spaces.gsub("\t", ' ' * @config.tab_width)
26
+ spaces.gsub("\t", " " * @config.tab_width)
27
27
  )
28
28
  end
29
29
 
@@ -25,7 +25,7 @@ module ERBLint
25
25
 
26
26
  def autocorrect(_processed_source, offense)
27
27
  lambda do |corrector|
28
- corrector.replace(offense.source_range, '')
28
+ corrector.replace(offense.source_range, "")
29
29
  end
30
30
  end
31
31
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require 'active_support/core_ext/class'
3
- require 'active_support/core_ext/module/delegation'
2
+ require "active_support/core_ext/class"
3
+ require "active_support/core_ext/module/delegation"
4
4
 
5
5
  module ERBLint
6
6
  class Reporter
@@ -5,7 +5,7 @@ module ERBLint
5
5
  class CompactReporter < Reporter
6
6
  def preview
7
7
  puts "Linting #{stats.files} files with "\
8
- "#{stats.linters} #{'autocorrectable ' if autocorrect}linters..."
8
+ "#{stats.linters} #{"autocorrectable " if autocorrect}linters..."
9
9
  end
10
10
 
11
11
  def show
@@ -9,7 +9,7 @@ module ERBLint
9
9
  def format_offense(filename, offense)
10
10
  <<~EOF
11
11
 
12
- #{offense.message}#{Rainbow(' (not autocorrected)').red if autocorrect}
12
+ #{offense.message}#{Rainbow(" (not autocorrected)").red if autocorrect}
13
13
  In file: #{filename}:#{offense.line_number}
14
14
  EOF
15
15
  end
@@ -8,7 +8,7 @@ module ERBLint
8
8
  def initialize(file_loader, config)
9
9
  @file_loader = file_loader
10
10
  @config = config || RunnerConfig.default
11
- raise ArgumentError, 'expect `config` to be a RunnerConfig instance' unless @config.is_a?(RunnerConfig)
11
+ raise ArgumentError, "expect `config` to be a RunnerConfig instance" unless @config.is_a?(RunnerConfig)
12
12
 
13
13
  linter_classes = LinterRegistry.linters.select { |klass| @config.for_linter(klass).enabled? }
14
14
  @linters = linter_classes.map do |linter_class|
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'erb_lint/runner_config_resolver'
3
+ require "erb_lint/runner_config_resolver"
4
4
 
5
5
  module ERBLint
6
6
  class RunnerConfig
@@ -9,7 +9,7 @@ module ERBLint
9
9
  def initialize(config = nil, file_loader = nil)
10
10
  @config = (config || {}).dup.deep_stringify_keys
11
11
 
12
- resolver.resolve_inheritance_from_gems(@config, @config.delete('inherit_gem'))
12
+ resolver.resolve_inheritance_from_gems(@config, @config.delete("inherit_gem"))
13
13
  resolver.resolve_inheritance(@config, file_loader) if file_loader
14
14
  @config.delete("inherit_from")
15
15
  end
@@ -24,7 +24,7 @@ module ERBLint
24
24
  elsif klass.is_a?(Class) && klass <= ERBLint::Linter
25
25
  klass.simple_name
26
26
  else
27
- raise ArgumentError, 'expected String or linter class'
27
+ raise ArgumentError, "expected String or linter class"
28
28
  end
29
29
  linter_klass = LinterRegistry.find_by_name(klass_name)
30
30
  raise Error, "#{klass_name}: linter not found (is it loaded?)" unless linter_klass
@@ -32,7 +32,7 @@ module ERBLint
32
32
  end
33
33
 
34
34
  def global_exclude
35
- @config['exclude'] || []
35
+ @config["exclude"] || []
36
36
  end
37
37
 
38
38
  def merge(other_config)
@@ -75,13 +75,13 @@ module ERBLint
75
75
  private
76
76
 
77
77
  def linters_config
78
- @config['linters'] || {}
78
+ @config["linters"] || {}
79
79
  end
80
80
 
81
81
  def config_hash_for_linter(klass_name)
82
82
  config_hash = linters_config[klass_name] || {}
83
- config_hash['exclude'] ||= []
84
- config_hash['exclude'].concat(global_exclude) if config_hash['exclude'].is_a?(Array)
83
+ config_hash["exclude"] ||= []
84
+ config_hash["exclude"].concat(global_exclude) if config_hash["exclude"].is_a?(Array)
85
85
  config_hash
86
86
  end
87
87
 
@@ -24,7 +24,7 @@
24
24
  module ERBLint
25
25
  class RunnerConfigResolver
26
26
  def resolve_inheritance(hash, file_loader)
27
- inherited_files = Array(hash['inherit_from'])
27
+ inherited_files = Array(hash["inherit_from"])
28
28
  base_configs(file_loader, inherited_files).reverse_each do |base_config|
29
29
  base_config.each do |k, v|
30
30
  next unless v.is_a?(Hash)
@@ -36,12 +36,12 @@ module ERBLint
36
36
 
37
37
  def resolve_inheritance_from_gems(hash, gems)
38
38
  (gems || {}).each_pair do |gem_name, config_path|
39
- raise(ArgumentError, "can't inherit configuration from the erb-lint gem") if gem_name == 'erb-lint'
39
+ raise(ArgumentError, "can't inherit configuration from the erb-lint gem") if gem_name == "erb-lint"
40
40
 
41
- hash['inherit_from'] = Array(hash['inherit_from'])
41
+ hash["inherit_from"] = Array(hash["inherit_from"])
42
42
  Array(config_path).reverse_each do |path|
43
43
  # Put gem configuration first so local configuration overrides it.
44
- hash['inherit_from'].unshift(gem_config_path(gem_name, path))
44
+ hash["inherit_from"].unshift(gem_config_path(gem_name, path))
45
45
  end
46
46
  end
47
47
  end
@@ -2,12 +2,12 @@
2
2
  module ERBLint
3
3
  class Stats
4
4
  attr_accessor :ignored,
5
- :found,
6
- :corrected,
7
- :exceptions,
8
- :linters,
9
- :files,
10
- :processed_files
5
+ :found,
6
+ :corrected,
7
+ :exceptions,
8
+ :linters,
9
+ :files,
10
+ :processed_files
11
11
 
12
12
  def initialize(
13
13
  ignored: 0,
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html/ast/node'
4
- require 'better_html/test_helper/ruby_node'
3
+ require "better_html/ast/node"
4
+ require "better_html/test_helper/ruby_node"
5
5
 
6
6
  module ERBLint
7
7
  module Utils
@@ -63,7 +63,7 @@ module ERBLint
63
63
  node_or_range
64
64
  else
65
65
  raise TypeError,
66
- 'Expected a Parser::Source::Range, Comment or ' \
66
+ "Expected a Parser::Source::Range, Comment or " \
67
67
  "Rubocop::AST::Node, got #{node_or_range.class}"
68
68
  end
69
69
  end
@@ -9,10 +9,10 @@ module ERBLint
9
9
  def html_options_to_tag_attributes(hash_node)
10
10
  hash_node.children.map do |pair_node|
11
11
  key_node, value_node = *pair_node
12
- key = ruby_to_erb(key_node, '=') { |s| s.tr('_', '-') }
13
- value = ruby_to_erb(value_node, '=') { |s| escape_quote(s) }
14
- [key, "\"#{value}\""].join('=')
15
- end.join(' ')
12
+ key = ruby_to_erb(key_node, "=") { |s| s.tr("_", "-") }
13
+ value = ruby_to_erb(value_node, "=") { |s| escape_quote(s) }
14
+ [key, "\"#{value}\""].join("=")
15
+ end.join(" ")
16
16
  end
17
17
 
18
18
  def ruby_to_erb(node, indicator = nil, &block)
@@ -43,7 +43,7 @@ module ERBLint
43
43
  end
44
44
 
45
45
  def escape_quote(str)
46
- str.gsub('"', '&quot;')
46
+ str.gsub('"', "&quot;")
47
47
  end
48
48
  end
49
49
  end
@@ -3,10 +3,10 @@
3
3
  module ERBLint
4
4
  module Utils
5
5
  module SeverityLevels
6
- SEVERITY_NAMES = %i[info refactor convention warning error fatal].freeze
6
+ SEVERITY_NAMES = [:info, :refactor, :convention, :warning, :error, :fatal].freeze
7
7
 
8
8
  SEVERITY_CODE_TABLE = { I: :info, R: :refactor, C: :convention,
9
- W: :warning, E: :error, F: :fatal }.freeze
9
+ W: :warning, E: :error, F: :fatal, }.freeze
10
10
 
11
11
  def severity_level_for_name(name)
12
12
  SEVERITY_NAMES.index(name || :error) + 1
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ERBLint
4
- VERSION = '0.1.0'
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erb_lint
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
  - Justin Chan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-26 00:00:00.000000000 Z
11
+ date: 2021-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: better_html