bibliothecary 8.7.4 → 8.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +21 -4
  3. data/.rubocop.yml +32 -941
  4. data/Gemfile +1 -1
  5. data/Rakefile +10 -0
  6. data/bibliothecary.gemspec +2 -2
  7. data/lib/bibliothecary/analyser.rb +6 -6
  8. data/lib/bibliothecary/cli.rb +8 -8
  9. data/lib/bibliothecary/configuration.rb +8 -8
  10. data/lib/bibliothecary/file_info.rb +2 -2
  11. data/lib/bibliothecary/multi_parsers/bundler_like_manifest.rb +1 -1
  12. data/lib/bibliothecary/multi_parsers/cyclonedx.rb +16 -17
  13. data/lib/bibliothecary/multi_parsers/dependencies_csv.rb +16 -16
  14. data/lib/bibliothecary/multi_parsers/json_runtime.rb +3 -3
  15. data/lib/bibliothecary/multi_parsers/spdx.rb +14 -14
  16. data/lib/bibliothecary/parsers/bower.rb +7 -7
  17. data/lib/bibliothecary/parsers/cargo.rb +15 -15
  18. data/lib/bibliothecary/parsers/carthage.rb +16 -16
  19. data/lib/bibliothecary/parsers/clojars.rb +7 -7
  20. data/lib/bibliothecary/parsers/cocoapods.rb +20 -20
  21. data/lib/bibliothecary/parsers/conda.rb +9 -9
  22. data/lib/bibliothecary/parsers/cpan.rb +12 -12
  23. data/lib/bibliothecary/parsers/cran.rb +12 -12
  24. data/lib/bibliothecary/parsers/dub.rb +8 -8
  25. data/lib/bibliothecary/parsers/elm.rb +8 -8
  26. data/lib/bibliothecary/parsers/go.rb +147 -67
  27. data/lib/bibliothecary/parsers/hackage.rb +13 -13
  28. data/lib/bibliothecary/parsers/haxelib.rb +4 -4
  29. data/lib/bibliothecary/parsers/hex.rb +11 -11
  30. data/lib/bibliothecary/parsers/julia.rb +4 -4
  31. data/lib/bibliothecary/parsers/maven.rb +88 -89
  32. data/lib/bibliothecary/parsers/meteor.rb +4 -4
  33. data/lib/bibliothecary/parsers/npm.rb +31 -31
  34. data/lib/bibliothecary/parsers/nuget.rb +44 -44
  35. data/lib/bibliothecary/parsers/packagist.rb +14 -14
  36. data/lib/bibliothecary/parsers/pub.rb +13 -13
  37. data/lib/bibliothecary/parsers/pypi.rb +71 -71
  38. data/lib/bibliothecary/parsers/rubygems.rb +15 -15
  39. data/lib/bibliothecary/parsers/shard.rb +13 -13
  40. data/lib/bibliothecary/parsers/swift_pm.rb +6 -6
  41. data/lib/bibliothecary/purl_util.rb +1 -1
  42. data/lib/bibliothecary/runner.rb +4 -4
  43. data/lib/bibliothecary/version.rb +1 -1
  44. data/lib/bibliothecary.rb +3 -3
  45. data/lib/sdl_parser.rb +5 -5
  46. metadata +2 -2
@@ -1,5 +1,5 @@
1
- require 'gemnasium/parser'
2
- require 'yaml'
1
+ require "gemnasium/parser"
2
+ require "yaml"
3
3
 
4
4
  module Bibliothecary
5
5
  module Parsers
@@ -13,58 +13,58 @@ module Bibliothecary
13
13
  def self.mapping
14
14
  {
15
15
  match_filename("Podfile") => {
16
- kind: 'manifest',
17
- parser: :parse_podfile
16
+ kind: "manifest",
17
+ parser: :parse_podfile,
18
18
  },
19
19
  match_extension(".podspec") => {
20
- kind: 'manifest',
20
+ kind: "manifest",
21
21
  parser: :parse_podspec,
22
- can_have_lockfile: false
22
+ can_have_lockfile: false,
23
23
  },
24
24
  match_filename("Podfile.lock") => {
25
- kind: 'lockfile',
26
- parser: :parse_podfile_lock
25
+ kind: "lockfile",
26
+ parser: :parse_podfile_lock,
27
27
  },
28
28
  match_extension(".podspec.json") => {
29
- kind: 'manifest',
29
+ kind: "manifest",
30
30
  parser: :parse_json_manifest,
31
- can_have_lockfile: false
32
- }
31
+ can_have_lockfile: false,
32
+ },
33
33
  }
34
34
  end
35
35
 
36
36
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
37
37
 
38
- def self.parse_podfile_lock(file_contents, options: {})
38
+ def self.parse_podfile_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
39
39
  manifest = YAML.load file_contents
40
- manifest['PODS'].map do |row|
40
+ manifest["PODS"].map do |row|
41
41
  pod = row.is_a?(String) ? row : row.keys.first
42
42
  match = pod.match(/(.+?)\s\((.+?)\)/i)
43
43
  {
44
- name: match[1].split('/').first,
44
+ name: match[1].split("/").first,
45
45
  requirement: match[2],
46
- type: 'runtime'
46
+ type: "runtime",
47
47
  }
48
48
  end.compact
49
49
  end
50
50
 
51
- def self.parse_podspec(file_contents, options: {})
51
+ def self.parse_podspec(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
52
52
  manifest = Gemnasium::Parser.send(:podspec, file_contents)
53
53
  parse_ruby_manifest(manifest)
54
54
  end
55
55
 
56
- def self.parse_podfile(file_contents, options: {})
56
+ def self.parse_podfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
57
57
  manifest = Gemnasium::Parser.send(:podfile, file_contents)
58
58
  parse_ruby_manifest(manifest)
59
59
  end
60
60
 
61
- def self.parse_json_manifest(file_contents, options: {})
61
+ def self.parse_json_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
62
62
  manifest = JSON.parse(file_contents)
63
- manifest['dependencies'].inject([]) do |deps, dep|
63
+ manifest["dependencies"].inject([]) do |deps, dep|
64
64
  deps.push({
65
65
  name: dep[0],
66
66
  requirement: dep[1],
67
- type: 'runtime'
67
+ type: "runtime",
68
68
  })
69
69
  end.uniq
70
70
  end
@@ -9,20 +9,20 @@ module Bibliothecary
9
9
  {
10
10
  match_filename("environment.yml") => {
11
11
  parser: :parse_conda,
12
- kind: "manifest"
12
+ kind: "manifest",
13
13
  },
14
14
  match_filename("environment.yaml") => {
15
15
  parser: :parse_conda,
16
- kind: "manifest"
16
+ kind: "manifest",
17
17
  },
18
18
  match_filename("environment.yml.lock") => {
19
19
  parser: :parse_conda_lockfile,
20
- kind: "lockfile"
20
+ kind: "lockfile",
21
21
  },
22
22
  match_filename("environment.yaml.lock") => {
23
23
  parser: :parse_conda_lockfile,
24
- kind: "lockfile"
25
- }
24
+ kind: "lockfile",
25
+ },
26
26
  }
27
27
  end
28
28
 
@@ -30,11 +30,11 @@ module Bibliothecary
30
30
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
31
31
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
32
32
 
33
- def self.parse_conda(file_contents, options: {})
33
+ def self.parse_conda(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
34
34
  parse_conda_with_kind(file_contents, "manifest")
35
35
  end
36
36
 
37
- def self.parse_conda_lockfile(file_contents, options: {})
37
+ def self.parse_conda_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
38
38
  parse_conda_with_kind(file_contents, "lockfile")
39
39
  end
40
40
 
@@ -48,12 +48,12 @@ module Bibliothecary
48
48
  response = Typhoeus.post(
49
49
  "#{host}/parse",
50
50
  headers: {
51
- ContentType: "multipart/form-data"
51
+ ContentType: "multipart/form-data",
52
52
  },
53
53
  body: {
54
54
  file: file_contents,
55
55
  # Unfortunately we do not get the filename in the mapping parsers, so hardcoding the file name depending on the kind
56
- filename: kind == "manifest" ? "environment.yml" : "environment.yml.lock"
56
+ filename: kind == "manifest" ? "environment.yml" : "environment.yml.lock",
57
57
  }
58
58
  )
59
59
  raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{host}/parse", response.response_code) unless response.success?
@@ -1,5 +1,5 @@
1
- require 'yaml'
2
- require 'json'
1
+ require "yaml"
2
+ require "json"
3
3
 
4
4
  module Bibliothecary
5
5
  module Parsers
@@ -9,28 +9,28 @@ module Bibliothecary
9
9
  def self.mapping
10
10
  {
11
11
  match_filename("META.json", case_insensitive: true) => {
12
- kind: 'manifest',
13
- parser: :parse_json_manifest
12
+ kind: "manifest",
13
+ parser: :parse_json_manifest,
14
14
  },
15
15
  match_filename("META.yml", case_insensitive: true) => {
16
- kind: 'manifest',
17
- parser: :parse_yaml_manifest
18
- }
16
+ kind: "manifest",
17
+ parser: :parse_yaml_manifest,
18
+ },
19
19
  }
20
20
  end
21
21
 
22
22
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
23
23
 
24
- def self.parse_json_manifest(file_contents, options: {})
24
+ def self.parse_json_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
25
25
  manifest = JSON.parse file_contents
26
- manifest['prereqs'].map do |_group, deps|
27
- map_dependencies(deps, 'requires', 'runtime')
26
+ manifest["prereqs"].map do |_group, deps|
27
+ map_dependencies(deps, "requires", "runtime")
28
28
  end.flatten
29
29
  end
30
30
 
31
- def self.parse_yaml_manifest(file_contents, options: {})
31
+ def self.parse_yaml_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
32
32
  manifest = YAML.load file_contents
33
- map_dependencies(manifest, 'requires', 'runtime')
33
+ map_dependencies(manifest, "requires", "runtime")
34
34
  end
35
35
  end
36
36
  end
@@ -1,4 +1,4 @@
1
- require 'deb_control'
1
+ require "deb_control"
2
2
 
3
3
  module Bibliothecary
4
4
  module Parsers
@@ -10,9 +10,9 @@ module Bibliothecary
10
10
  def self.mapping
11
11
  {
12
12
  match_filename("DESCRIPTION", case_insensitive: true) => {
13
- kind: 'manifest',
14
- parser: :parse_description
15
- }
13
+ kind: "manifest",
14
+ parser: :parse_description,
15
+ },
16
16
  }
17
17
  end
18
18
 
@@ -20,23 +20,23 @@ module Bibliothecary
20
20
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
21
21
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
22
22
 
23
- def self.parse_description(file_contents, options: {})
23
+ def self.parse_description(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
24
24
  manifest = DebControl::ControlFileBase.parse(file_contents)
25
- parse_section(manifest, 'Depends') +
26
- parse_section(manifest, 'Imports') +
27
- parse_section(manifest, 'Suggests') +
28
- parse_section(manifest, 'Enhances')
25
+ parse_section(manifest, "Depends") +
26
+ parse_section(manifest, "Imports") +
27
+ parse_section(manifest, "Suggests") +
28
+ parse_section(manifest, "Enhances")
29
29
  end
30
30
 
31
31
  def self.parse_section(manifest, name)
32
32
  return [] unless manifest.first[name]
33
- deps = manifest.first[name].delete("\n").split(',').map(&:strip)
33
+ deps = manifest.first[name].delete("\n").split(",").map(&:strip)
34
34
  deps.map do |dependency|
35
35
  dep = dependency.match(REQUIRE_REGEXP)
36
36
  {
37
37
  name: dep[1],
38
- requirement: dep[2] || '*',
39
- type: name.downcase
38
+ requirement: dep[2] || "*",
39
+ type: name.downcase,
40
40
  }
41
41
  end
42
42
  end
@@ -1,5 +1,5 @@
1
- require 'json'
2
- require 'sdl_parser'
1
+ require "json"
2
+ require "sdl_parser"
3
3
 
4
4
  module Bibliothecary
5
5
  module Parsers
@@ -10,19 +10,19 @@ module Bibliothecary
10
10
  def self.mapping
11
11
  {
12
12
  match_filename("dub.json") => {
13
- kind: 'manifest',
14
- parser: :parse_json_runtime_manifest
13
+ kind: "manifest",
14
+ parser: :parse_json_runtime_manifest,
15
15
  },
16
16
  match_filename("dub.sdl") => {
17
- kind: 'manifest',
18
- parser: :parse_sdl_manifest
19
- }
17
+ kind: "manifest",
18
+ parser: :parse_sdl_manifest,
19
+ },
20
20
  }
21
21
  end
22
22
 
23
23
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
24
24
 
25
- def self.parse_sdl_manifest(file_contents, options: {})
25
+ def self.parse_sdl_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
26
26
  SdlParser.new(:runtime, file_contents).dependencies
27
27
  end
28
28
  end
@@ -1,4 +1,4 @@
1
- require 'json'
1
+ require "json"
2
2
 
3
3
  module Bibliothecary
4
4
  module Parsers
@@ -9,25 +9,25 @@ module Bibliothecary
9
9
  def self.mapping
10
10
  {
11
11
  match_filenames("elm-package.json", "elm_dependencies.json") => {
12
- kind: 'manifest',
13
- parser: :parse_json_runtime_manifest
12
+ kind: "manifest",
13
+ parser: :parse_json_runtime_manifest,
14
14
  },
15
15
  match_filename("elm-stuff/exact-dependencies.json") => {
16
- kind: 'lockfile',
17
- parser: :parse_json_lock
18
- }
16
+ kind: "lockfile",
17
+ parser: :parse_json_lock,
18
+ },
19
19
  }
20
20
  end
21
21
 
22
22
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
23
23
 
24
- def self.parse_json_lock(file_contents, options: {})
24
+ def self.parse_json_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
25
25
  manifest = JSON.parse file_contents
26
26
  manifest.map do |name, requirement|
27
27
  {
28
28
  name: name,
29
29
  requirement: requirement,
30
- type: 'runtime'
30
+ type: "runtime",
31
31
  }
32
32
  end
33
33
  end
@@ -1,5 +1,5 @@
1
- require 'yaml'
2
- require 'json'
1
+ require "yaml"
2
+ require "json"
3
3
 
4
4
  module Bibliothecary
5
5
  module Parsers
@@ -7,61 +7,66 @@ module Bibliothecary
7
7
  include Bibliothecary::Analyser
8
8
 
9
9
  GPM_REGEXP = /^(.+)\s+(.+)$/
10
- GOMOD_REGEX = /^(require\s+)?(.+)\s+(.+)$/
11
- GOMOD_IGNORABLE_REGEX = /^(\/\/|module\s|go\s|exclude\s|replace\s|require\s+\(|\))/m
12
- GOSUM_REGEX = /^(.+)\s+(.+)\s+(.+)$/
10
+ GOMOD_REPLACEMENT_SEPARATOR_REGEXP = /\s=>\s/
11
+ GOMOD_DEP_REGEXP = /(?<name>\S+)\s?(?<requirement>[^\s=>]+)?/ # the " =>" negative character class is to make sure we don't capture the delimiter for "replace" deps
12
+ GOMOD_SINGLELINE_DEP_REGEXP = /^(?<category>require|exclude|replace|retract)\s+#{GOMOD_DEP_REGEXP}.*$/
13
+ GOMOD_MULTILINE_DEP_REGEXP = /^#{GOMOD_DEP_REGEXP}.*$/
14
+ GOMOD_MULTILINE_START_REGEXP = /^(?<category>require|exclude|replace|retract)\s+\(/
15
+ GOMOD_MULTILINE_END_REGEXP = /^\)/
16
+ GOMOD_COMMENT_REGEXP = /(\/\/(.*))/
17
+ GOSUM_REGEXP = /^(.+)\s+(.+)\s+(.+)$/
13
18
 
14
19
  def self.mapping
15
20
  {
16
21
  # Go Modules (recommended)
17
22
  match_filename("go.mod") => {
18
- kind: 'manifest',
19
- parser: :parse_go_mod
23
+ kind: "manifest",
24
+ parser: :parse_go_mod,
20
25
  },
21
26
  match_filename("go.sum") => {
22
- kind: 'lockfile',
23
- parser: :parse_go_sum
27
+ kind: "lockfile",
28
+ parser: :parse_go_sum,
24
29
  },
25
30
  # Glide (unmaintained: https://github.com/Masterminds/glide#go-modules)
26
31
  match_filename("glide.yaml") => {
27
- kind: 'manifest',
28
- parser: :parse_glide_yaml
32
+ kind: "manifest",
33
+ parser: :parse_glide_yaml,
29
34
  },
30
35
  match_filename("glide.lock") => {
31
- kind: 'lockfile',
32
- parser: :parse_glide_lockfile
36
+ kind: "lockfile",
37
+ parser: :parse_glide_lockfile,
33
38
  },
34
39
  # Godep (unmaintained: https://github.com/tools/godep)
35
40
  match_filename("Godeps/Godeps.json") => {
36
- kind: 'manifest',
37
- parser: :parse_godep_json
41
+ kind: "manifest",
42
+ parser: :parse_godep_json,
38
43
  },
39
44
  match_filename("Godeps", case_insensitive: true) => {
40
- kind: 'manifest',
41
- parser: :parse_gpm
45
+ kind: "manifest",
46
+ parser: :parse_gpm,
42
47
  },
43
48
  # Govendor (unmaintained: https://github.com/kardianos/govendor)
44
49
  match_filename("vendor/manifest") => {
45
- kind: 'manifest',
46
- parser: :parse_gb_manifest
50
+ kind: "manifest",
51
+ parser: :parse_gb_manifest,
47
52
  },
48
53
  match_filename("vendor/vendor.json") => {
49
- kind: 'manifest',
50
- parser: :parse_govendor
54
+ kind: "manifest",
55
+ parser: :parse_govendor,
51
56
  },
52
57
  # Go dep (deprecated: https://github.com/golang/dep#dep)
53
58
  match_filename("Gopkg.toml") => {
54
- kind: 'manifest',
55
- parser: :parse_dep_toml
59
+ kind: "manifest",
60
+ parser: :parse_dep_toml,
56
61
  },
57
62
  match_filename("Gopkg.lock") => {
58
- kind: 'lockfile',
59
- parser: :parse_dep_lockfile
63
+ kind: "lockfile",
64
+ parser: :parse_dep_lockfile,
60
65
  },
61
66
  match_filename("go-resolved-dependencies.json") => {
62
- kind: 'lockfile',
63
- parser: :parse_go_resolved
64
- }
67
+ kind: "lockfile",
68
+ parser: :parse_go_resolved,
69
+ },
65
70
  }
66
71
  end
67
72
 
@@ -69,97 +74,172 @@ module Bibliothecary
69
74
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
70
75
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
71
76
 
72
- def self.parse_godep_json(file_contents, options: {})
77
+ def self.parse_godep_json(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
73
78
  manifest = JSON.parse file_contents
74
- map_dependencies(manifest, 'Deps', 'ImportPath', 'Rev', 'runtime')
79
+ map_dependencies(manifest, "Deps", "ImportPath", "Rev", "runtime")
75
80
  end
76
81
 
77
- def self.parse_gpm(file_contents, options: {})
82
+ def self.parse_gpm(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
78
83
  deps = []
79
84
  file_contents.split("\n").each do |line|
80
- match = line.gsub(/(\#(.*))/, '').match(GPM_REGEXP)
85
+ match = line.gsub(/(\#(.*))/, "").match(GPM_REGEXP)
81
86
  next unless match
82
87
  deps << {
83
88
  name: match[1].strip,
84
- requirement: match[2].strip || '*',
85
- type: 'runtime'
89
+ requirement: match[2].strip || "*",
90
+ type: "runtime",
86
91
  }
87
92
  end
88
93
  deps
89
94
  end
90
95
 
91
- def self.parse_govendor(file_contents, options: {})
96
+ def self.parse_govendor(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
92
97
  manifest = JSON.load file_contents
93
- map_dependencies(manifest, 'package', 'path', 'revision', 'runtime')
98
+ map_dependencies(manifest, "package", "path", "revision", "runtime")
94
99
  end
95
100
 
96
- def self.parse_glide_yaml(file_contents, options: {})
101
+ def self.parse_glide_yaml(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
97
102
  manifest = YAML.load file_contents
98
- map_dependencies(manifest, 'import', 'package', 'version', 'runtime') +
99
- map_dependencies(manifest, 'devImports', 'package', 'version', 'development')
103
+ map_dependencies(manifest, "import", "package", "version", "runtime") +
104
+ map_dependencies(manifest, "devImports", "package", "version", "development")
100
105
  end
101
106
 
102
- def self.parse_glide_lockfile(file_contents, options: {})
107
+ def self.parse_glide_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
103
108
  manifest = YAML.load file_contents
104
- map_dependencies(manifest, 'imports', 'name', 'version', 'runtime')
109
+ map_dependencies(manifest, "imports", "name", "version", "runtime")
105
110
  end
106
111
 
107
- def self.parse_gb_manifest(file_contents, options: {})
112
+ def self.parse_gb_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
108
113
  manifest = JSON.parse file_contents
109
- map_dependencies(manifest, 'dependencies', 'importpath', 'revision', 'runtime')
114
+ map_dependencies(manifest, "dependencies", "importpath", "revision", "runtime")
110
115
  end
111
116
 
112
- def self.parse_dep_toml(file_contents, options: {})
117
+ def self.parse_dep_toml(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
113
118
  manifest = Tomlrb.parse file_contents
114
- map_dependencies(manifest, 'constraint', 'name', 'version', 'runtime')
119
+ map_dependencies(manifest, "constraint", "name", "version", "runtime")
115
120
  end
116
121
 
117
- def self.parse_dep_lockfile(file_contents, options: {})
122
+ def self.parse_dep_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
118
123
  manifest = Tomlrb.parse file_contents
119
- map_dependencies(manifest, 'projects', 'name', 'revision', 'runtime')
124
+ map_dependencies(manifest, "projects", "name", "revision", "runtime")
120
125
  end
121
126
 
122
- def self.parse_go_mod(file_contents, options: {})
123
- deps = []
124
- file_contents.lines.map(&:strip).each do |line|
125
- next if line.match(GOMOD_IGNORABLE_REGEX)
126
- if match = line.gsub(/(\/\/(.*))/, '').match(GOMOD_REGEX)
127
- deps << {
128
- name: match[2].strip,
129
- requirement: match[3].strip || '*',
130
- type: 'runtime'
131
- }
127
+ def self.parse_go_mod(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
128
+ categorized_deps = parse_go_mod_categorized_deps(file_contents)
129
+
130
+ deps = categorized_deps["require"]
131
+ .map do |dep|
132
+ # NOTE: A "replace" directive doesn't add the dep to the module graph unless the original dep is also in a "require" directive,
133
+ # so we need to track down replacements here and use those instead of the originals, if present.
134
+ #
135
+ # NOTE: The "replace" directive doesn't actually change the version reported from Go (e.g. "go mod graph"), it only changes
136
+ # the *source code*. So by replacing the deps here, we're giving more honest results than you'd get when asking go
137
+ # about the versions used.
138
+ replaced_dep = categorized_deps["replace"]
139
+ .find do |replacement_dep|
140
+ replacement_dep[:original_name] == dep[:name] &&
141
+ (replacement_dep[:original_requirement] == "*" || replacement_dep[:original_requirement] == dep[:requirement])
142
+ end
143
+
144
+ replaced_dep || dep
132
145
  end
133
- end
134
- deps
146
+
147
+ return deps
135
148
  end
136
149
 
137
- def self.parse_go_sum(file_contents, options: {})
150
+ def self.parse_go_mod_categorized_deps(file_contents)
151
+ current_multiline_category = nil
152
+ # docs: https://go.dev/ref/mod#go-mod-file-require
153
+ categorized_deps = {
154
+ "require" => [],
155
+ "exclude" => [], # these deps are not necessarily used by the module
156
+ "replace" => [], # these deps are not necessarily used by the module
157
+ "retract" => [], # TODO: these are not parsed correctly right now, but they shouldn't be returned in list of deps anyway.
158
+ }
159
+ file_contents
160
+ .lines
161
+ .reject { |line| line =~ /^#{GOMOD_COMMENT_REGEXP}/ } # ignore comment lines
162
+ .map { |line| line.strip.gsub(GOMOD_COMMENT_REGEXP, "") } # strip out trailing comments
163
+ .each do |line|
164
+ if line.match(GOMOD_MULTILINE_END_REGEXP) # detect the end of a multiline
165
+ current_multiline_category = nil
166
+ elsif (match = line.match(GOMOD_MULTILINE_START_REGEXP)) # or, detect the start of a multiline
167
+ current_multiline_category = match[1]
168
+ elsif (match = line.match(GOMOD_SINGLELINE_DEP_REGEXP)) # or, detect a singleline dep
169
+ categorized_deps[match[:category]] << go_mod_category_relative_dep(category: match[:category], line: line, match: match)
170
+ elsif (current_multiline_category && match = line.match(GOMOD_MULTILINE_DEP_REGEXP)) # otherwise, parse the multiline dep
171
+ categorized_deps[current_multiline_category] << go_mod_category_relative_dep(category: current_multiline_category, line: line, match: match)
172
+ end
173
+ end
174
+ categorized_deps
175
+ end
176
+
177
+ def self.parse_go_sum(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
138
178
  deps = []
139
179
  file_contents.lines.map(&:strip).each do |line|
140
- if match = line.match(GOSUM_REGEX)
180
+ if (match = line.match(GOSUM_REGEXP))
141
181
  deps << {
142
182
  name: match[1].strip,
143
- requirement: match[2].strip.split('/').first || '*',
144
- type: 'runtime'
183
+ requirement: match[2].strip.split("/").first || "*",
184
+ type: "runtime",
145
185
  }
146
186
  end
147
187
  end
148
188
  deps.uniq
149
189
  end
150
190
 
151
- def self.parse_go_resolved(file_contents, options: {})
191
+ def self.parse_go_resolved(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
152
192
  JSON.parse(file_contents)
153
193
  .select { |dep| dep["Main"] != "true" }
154
- .map { |dep| { name: dep["Path"], requirement: dep["Version"], type: dep.fetch("Scope") { "runtime" } } }
194
+ .map do |dep|
195
+ if dep["Replace"].is_a?(String) && dep["Replace"] != "<nil>" && dep["Replace"] != ""
196
+ # NOTE: The "replace" directive doesn't actually change the version reported from Go (e.g. "go mod graph"), it only changes
197
+ # the *source code*. So by replacing the deps here, we're giving more honest results than you'd get when asking go
198
+ # about the versions used.
199
+ name, requirement = dep["Replace"].split(" ", 2)
200
+ requirement = "*" if requirement.to_s.strip == ""
201
+ { name: name, requirement: requirement, original_name: dep["Path"], original_requirement: dep["Version"], type: dep.fetch("Scope") { "runtime" } }
202
+ else
203
+ { name: dep["Path"], requirement: dep["Version"], type: dep.fetch("Scope") { "runtime" } }
204
+ end
205
+ end
155
206
  end
156
207
 
157
208
  def self.map_dependencies(manifest, attr_name, dep_attr_name, version_attr_name, type)
158
209
  manifest.fetch(attr_name,[]).map do |dependency|
159
210
  {
160
211
  name: dependency[dep_attr_name],
161
- requirement: dependency[version_attr_name] || '*',
162
- type: type
212
+ requirement: dependency[version_attr_name] || "*",
213
+ type: type,
214
+ }
215
+ end
216
+ end
217
+
218
+ # Returns our standard-ish dep Hash based on the category of dep matched ("require", "replace", etc.)
219
+ def self.go_mod_category_relative_dep(category:, line:, match:)
220
+ case category
221
+ when "replace"
222
+ replacement_dep = line.split(GOMOD_REPLACEMENT_SEPARATOR_REGEXP, 2).last
223
+ replacement_match = replacement_dep.match(GOMOD_DEP_REGEXP)
224
+ {
225
+ original_name: match[:name],
226
+ original_requirement: match[:requirement] || "*",
227
+ name: replacement_match[:name],
228
+ requirement: replacement_match[:requirement] || "*",
229
+ type: "runtime",
230
+ }
231
+ when "retract"
232
+ {
233
+ name: match[:name],
234
+ requirement: match[:requirement] || "*",
235
+ type: "runtime",
236
+ deprecated: true,
237
+ }
238
+ else
239
+ {
240
+ name: match[:name],
241
+ requirement: match[:requirement] || "*",
242
+ type: "runtime",
163
243
  }
164
244
  end
165
245
  end