diff --git a/lib/ast_transform/instruction_sequence.rb b/lib/ast_transform/instruction_sequence.rb index ab875a8..795d2b3 100644 --- a/lib/ast_transform/instruction_sequence.rb +++ b/lib/ast_transform/instruction_sequence.rb @@ -16,7 +16,7 @@ def source_to_transformed_iseq(source, source_path) rewritten_source = transformer.transform_file_source(source, source_path, rewritten_file_pathname.to_s) write(rewritten_source, rewritten_file_pathname) - RubyVM::InstructionSequence.compile(rewritten_source, rewritten_file_pathname.to_s, rewritten_file_pathname.to_s) + RubyVM::InstructionSequence.compile(rewritten_source, source_path, source_path) end def write_pathname(file_path) diff --git a/lib/ast_transform/instruction_sequence/mixin.rb b/lib/ast_transform/instruction_sequence/mixin.rb index fa647f5..a25d839 100644 --- a/lib/ast_transform/instruction_sequence/mixin.rb +++ b/lib/ast_transform/instruction_sequence/mixin.rb @@ -10,9 +10,12 @@ module Mixin def load_iseq(source_path) return ASTTransform::MixinUtils.try_super(self, :load_iseq, source_path) if source_path == __FILE__ - source = File.read(source_path) + # Binary read avoids encoding errors during the fast-path check below. + # Downstream (Prism, RubyVM::InstructionSequence) handle encoding natively + # via magic comments, so we never need to set it ourselves. + source = File.binread(source_path) - return ASTTransform::MixinUtils.try_super(self, :load_iseq, source_path) unless source =~ /transform!/ + return ASTTransform::MixinUtils.try_super(self, :load_iseq, source_path) unless source.include?('transform!'.b) ASTTransform::InstructionSequence.source_to_transformed_iseq(source, source_path) end diff --git a/lib/ast_transform/source_map.rb b/lib/ast_transform/source_map.rb index 4e4b627..4833ad7 100644 --- a/lib/ast_transform/source_map.rb +++ b/lib/ast_transform/source_map.rb @@ -11,6 +11,7 @@ class << self # @return [void] def register_source_map(source_map) source_maps[source_map.transformed_file_path] = source_map + source_maps[source_map.source_file_path] = source_map nil end diff --git a/test/ast_transform/instruction_sequence_test.rb b/test/ast_transform/instruction_sequence_test.rb new file mode 100644 index 0000000..b7a927e --- /dev/null +++ b/test/ast_transform/instruction_sequence_test.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'tempfile' +require 'ast_transform/instruction_sequence' +require 'ast_transform/instruction_sequence/mixin' +require 'ast_transform/transformation' + +module ASTTransform + class InstructionSequenceTest < Minitest::Test + extend ASTTransform::Declarative + + class ClassWithMixin + include ASTTransform::InstructionSequence::Mixin + end + + def setup + @loader = ClassWithMixin.new + end + + test "compiled iseq reports original source path" do + source = "1 + 2\n" + source_path = File.expand_path("test/fixtures/dummy.rb") + + iseq = ASTTransform::InstructionSequence.source_to_transformed_iseq(source, source_path) + + assert_equal source_path, iseq.path, + "ISeq should report the original source path, not the tmp/ rewritten path" + end + + test "source_to_transformed_iseq handles binary-encoded source" do + source = "# café résumé\n1 + 2\n".dup.force_encoding('ASCII-8BIT') + source_path = File.expand_path("test/fixtures/encoding_test.rb") + + iseq = ASTTransform::InstructionSequence.source_to_transformed_iseq(source, source_path) + + assert_equal source_path, iseq.path + end + + test "load_iseq skips non-ASCII files without transform!" do + tmpfile = Tempfile.new(['non_ascii', '.rb']) + tmpfile.write("# café résumé\nx = 1\n") + tmpfile.close + + iseq = @loader.load_iseq(tmpfile.path) + + assert_nil iseq + ensure + tmpfile&.unlink + end + + test "load_iseq processes non-ASCII files with transform! without encoding errors" do + tmpfile = Tempfile.new(['non_ascii_transform', '.rb']) + tmpfile.write("# café résumé\nx = 1\n# transform!\n") + tmpfile.close + + iseq = @loader.load_iseq(tmpfile.path) + + assert_instance_of RubyVM::InstructionSequence, iseq + assert_equal tmpfile.path, iseq.path + ensure + tmpfile&.unlink + end + end +end diff --git a/test/ast_transform/source_map_test.rb b/test/ast_transform/source_map_test.rb index d70ed03..2375896 100644 --- a/test/ast_transform/source_map_test.rb +++ b/test/ast_transform/source_map_test.rb @@ -105,6 +105,20 @@ def run(node) assert_equal 1, source_map.line(2) end + test "source map is retrievable by source file path" do + transformer = ASTTransform::Transformer.new + + source = <<~HEREDOC + method_call + HEREDOC + + transformer.transform_file_source(source, '/original/path.rb', '/transformed/path.rb') + + source_map = ASTTransform::SourceMap.for_file_path('/original/path.rb') + refute_nil source_map, "source map should be retrievable by source file path" + assert_equal '/original/path.rb', source_map.source_file_path + end + test "#line returns nil when transformation creates nodes that don't contain previous nodes" do transformation = Class.new(ASTTransform::AbstractTransformation) do def run(node)