Sitelet https://github.com/opal/opal/pull/2807
Skip to content

Add IO.foreach and IO.readlines on Node.js - #2807

Draft
elia wants to merge 1 commit into
masterfrom
elia/1845-io-foreach
Draft

elia wants to merge 1 commit into
masterfrom
elia/1845-io-foreach

Conversation

@elia

@elia elia commented Sep 13, 2026

Copy link
Copy Markdown
Member

Fixes #1845.

What changed

IO.foreach reads a file line by line without materialising the whole thing as an array, and is the form most Ruby code reaches for. It did not exist on Node.js, so it raised NoMethodError: undefined method 'foreach' for IO. The class-level IO.readlines was missing for the same reason, so both are added here.

Both delegate to File.open's block form, which closes the handle, and to the existing IO#each_line and IO#readlines. The separator handling and the enumerator behaviour come from code that is already tested rather than from a second implementation, which is why the diff is 16 lines.

IO#each_line landed in #2309, which is what made this a thin delegation rather than real work.

Behaviour checked against MRI:

case result
yields each line to a block "a\n" "b\n" "c\n"
returns nil when given a block nil
returns an Enumerator without a block Enumerator
custom separator, IO.foreach(f, 'b') "a\nb", "\nc\n"
last line with no trailing separator ["no-newline"]
empty file yields nothing
missing file Errno::ENOENT
IO.readlines ["a\n", "b\n", "c\n"]

File.foreach inherits both, since MRI defines them on IO's singleton and File subclasses it. Verified rather than assumed.

How to test

printf 'a\nb\nc\n' > /tmp/f.txt
bundle exec opal -rnodejs -e "IO.foreach('/tmp/f.txt') { |l| p l }"

Verification I ran locally:

  • bundle exec rake minitest_node_nodejs — 87 runs, 160 assertions, 0 failures
  • bundle exec rake mspec_nodejs — 14626 examples, 0 failures
  • bundle exec rubocop — 226 files, no offenses

Seven tests added to test/nodejs/test_io.rb, covering each row of the table above. Reverting just stdlib/nodejs/io.rb and rerunning turns them red with NoMethodError: undefined method 'foreach' for IO, so they pin the feature rather than the implementation.

Note for anyone rerunning these: test/nodejs/ is driven by minitest_node_nodejs. The similarly named minitest_cruby_nodejs runs the CRuby suite instead and passes whether or not this change is present.

🤖 Generated with Claude Code

IO.foreach reads a file line by line without holding the whole thing as
an array, and is the form most Ruby code reaches for. Neither it nor the
class-level IO.readlines existed on Node.js, so both raised NoMethodError.

Both delegate to File.open's block form, which closes the handle, and to
the existing IO#each_line and IO#readlines, so the separator handling and
the enumerator behaviour come from code that is already tested rather
than from a second implementation.

Matching MRI, foreach returns an Enumerator when called without a block,
returns nil when given one, honours a custom separator, and keeps a last
line that has no trailing separator. File.foreach inherits both, since
MRI defines them on IO's singleton and File subclasses it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement IO.foreach

1 participant