Skip to content

Commit 2ca7b4d

Browse files
authored
fix: Gemfile / Gemspec parser to ignore commented dependencies (#249)
1 parent 454b8ea commit 2ca7b4d

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

pkg/deps/ruby.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ func hasGemspec(dir string) bool {
213213
return false
214214
}
215215

216-
var gemspecRuntimeRe = regexp.MustCompile(`(?m)\badd_(?:runtime_)?dependency\s*\(?\s*["']([^"']+)["']`)
216+
var gemspecRuntimeRe = regexp.MustCompile(`\badd_(?:runtime_)?dependency\s*\(?\s*["']([^"']+)["']`)
217217

218218
func runtimeDepsFromGemspecs(dir string) ([]string, error) {
219219
entries, err := os.ReadDir(dir)
@@ -225,15 +225,26 @@ func runtimeDepsFromGemspecs(dir string) ([]string, error) {
225225
if e.IsDir() || !strings.HasSuffix(e.Name(), ".gemspec") {
226226
continue
227227
}
228-
b, err := os.ReadFile(filepath.Join(dir, e.Name()))
228+
path := filepath.Join(dir, e.Name())
229+
f, err := os.Open(path)
229230
if err != nil {
230231
return nil, err
231232
}
232-
for _, m := range gemspecRuntimeRe.FindAllStringSubmatch(string(b), -1) {
233-
if len(m) == 2 {
233+
scanner := bufio.NewScanner(f)
234+
for scanner.Scan() {
235+
line := scanner.Text()
236+
trimLeft := strings.TrimLeft(line, " \t")
237+
if strings.HasPrefix(trimLeft, "#") {
238+
continue // ignore commented-out lines
239+
}
240+
if m := gemspecRuntimeRe.FindStringSubmatch(line); len(m) == 2 {
234241
runtime[m[1]] = struct{}{}
235242
}
236243
}
244+
_ = f.Close()
245+
if err := scanner.Err(); err != nil {
246+
return nil, err
247+
}
237248
}
238249
res := make([]string, 0, len(runtime))
239250
for k := range runtime {

pkg/deps/ruby_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,3 +242,53 @@ func TestRubyLibraryWithNoRuntimeDependenciesIncludesNone(t *testing.T) {
242242
t.Fatalf("expected 0 dependencies for library with no runtime deps, got %d", got)
243243
}
244244
}
245+
246+
func TestGemspecIgnoresCommentedRuntimeDependencies(t *testing.T) {
247+
resolver := new(GemfileLockResolver)
248+
249+
// Prepare a library project whose gemspec contains a commented runtime dependency line
250+
dir := t.TempDir()
251+
lockContent := "" +
252+
"GEM\n" +
253+
" remote: https://rubygems.org/\n" +
254+
" specs:\n" +
255+
" rake (13.0.6)\n" +
256+
"\n" +
257+
"PLATFORMS\n" +
258+
" ruby\n" +
259+
"\n" +
260+
"DEPENDENCIES\n" +
261+
" rake\n" +
262+
"\n" +
263+
"BUNDLED WITH\n" +
264+
" 2.4.10\n"
265+
if err := writeFileRuby(filepath.Join(dir, "Gemfile.lock"), lockContent); err != nil {
266+
t.Fatal(err)
267+
}
268+
269+
gemspec := "" +
270+
"Gem::Specification.new do |spec|\n" +
271+
" spec.name = \"sample\"\n" +
272+
" spec.version = \"0.1.0\"\n" +
273+
" spec.summary = \"Sample gem\"\n" +
274+
" spec.description = \"Sample\"\n" +
275+
" spec.authors = [\"Test\"]\n" +
276+
" spec.files = []\n" +
277+
" # spec.add_dependency(\"git\", \">= 1.19.1\")\n" +
278+
"end\n"
279+
if err := writeFileRuby(filepath.Join(dir, "sample.gemspec"), gemspec); err != nil {
280+
t.Fatal(err)
281+
}
282+
283+
lock := filepath.Join(dir, "Gemfile.lock")
284+
cfg := &ConfigDeps{Files: []string{lock}}
285+
report := Report{}
286+
if err := resolver.Resolve(lock, cfg, &report); err != nil {
287+
t.Fatal(err)
288+
}
289+
290+
// There are no runtime dependencies in gemspec; the commented one must be ignored
291+
if got := len(report.Resolved) + len(report.Skipped); got != 0 {
292+
t.Fatalf("expected 0 dependencies when runtime deps are only commented, got %d", got)
293+
}
294+
}

0 commit comments

Comments
 (0)