Class: Astel::SourceFile

Inherits:
Object
  • Object
show all
Defined in:
lib/astel/source_file.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(code, path:, version: nil) ⇒ SourceFile

Returns a new instance of SourceFile.



17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/astel/source_file.rb', line 17

def initialize(code, path:, version: nil)
  @path = path.to_s.freeze
  @version = normalize_version(version).freeze if version
  raw_source = code.dup.freeze
  options = { filepath: @path }
  options[:version] = @version if @version
  @parse_result = Prism.parse(raw_source, **options)
  @source = @parse_result.source.source.freeze
  @ast = @parse_result.value
  @comments = @parse_result.comments.freeze
  @errors = @parse_result.errors.freeze
  @lines = nil
end

Instance Attribute Details

#astObject (readonly)

Returns the value of attribute ast.



7
8
9
# File 'lib/astel/source_file.rb', line 7

def ast
  @ast
end

#commentsObject (readonly)

Returns the value of attribute comments.



7
8
9
# File 'lib/astel/source_file.rb', line 7

def comments
  @comments
end

#errorsObject (readonly)

Returns the value of attribute errors.



7
8
9
# File 'lib/astel/source_file.rb', line 7

def errors
  @errors
end

#parse_resultObject (readonly)

Returns the value of attribute parse_result.



7
8
9
# File 'lib/astel/source_file.rb', line 7

def parse_result
  @parse_result
end

#pathObject (readonly)

Returns the value of attribute path.



7
8
9
# File 'lib/astel/source_file.rb', line 7

def path
  @path
end

#sourceObject (readonly)

Returns the value of attribute source.



7
8
9
# File 'lib/astel/source_file.rb', line 7

def source
  @source
end

#versionObject (readonly)

Returns the value of attribute version.



7
8
9
# File 'lib/astel/source_file.rb', line 7

def version
  @version
end

Class Method Details

.from_string(code, path: '(string)', version: nil) ⇒ Object



13
14
15
# File 'lib/astel/source_file.rb', line 13

def self.from_string(code, path: '(string)', version: nil)
  new(code, path: path, version: version)
end

.parse(path:, version: nil) ⇒ Object



9
10
11
# File 'lib/astel/source_file.rb', line 9

def self.parse(path:, version: nil)
  from_string(File.binread(path), path: path, version: version)
end

Instance Method Details

#attach_comments!Object



63
64
65
66
67
68
69
# File 'lib/astel/source_file.rb', line 63

def attach_comments!
  return self if @comments_attached

  @parse_result.attach_comments!
  @comments_attached = true
  self
end

#bofObject



39
40
41
# File 'lib/astel/source_file.rb', line 39

def bof
  Location.point(offset: 0, line: 1, column: 0)
end

#column_at(byte_offset) ⇒ Object



76
77
78
79
# File 'lib/astel/source_file.rb', line 76

def column_at(byte_offset)
  validate_offset!(byte_offset)
  @parse_result.source.column(byte_offset)
end

#first_line_locationObject



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/astel/source_file.rb', line 43

def first_line_location
  line = lines.first || ''
  Location.new(
    start_offset: 0,
    end_offset: line.bytesize,
    start_line: 1,
    start_column: 0,
    end_line: 1,
    end_column: line.delete_suffix("\n").delete_suffix("\r").bytesize
  )
end

#indent_unitObject



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/astel/source_file.rb', line 119

def indent_unit
  @indent_unit ||= begin
    indentations = lines.filter_map { |line| line[/\A[ \t]+/] }
    if indentations.any? { |indentation| indentation.include?("\t") }
      "\t"
    else
      widths = [0, *indentations.map(&:bytesize)].uniq.sort
      width = widths.each_cons(2).map { |left, right| right - left }.min
      width ? ' ' * width : '  '
    end
  end
end

#indentation_at(byte_offset) ⇒ Object



114
115
116
117
# File 'lib/astel/source_file.rb', line 114

def indentation_at(byte_offset)
  start_offset = line_start(byte_offset)
  source.byteslice(start_offset, line_end(byte_offset) - start_offset)[/\A[ \t]*/]
end

#line_at(byte_offset) ⇒ Object



71
72
73
74
# File 'lib/astel/source_file.rb', line 71

def line_at(byte_offset)
  validate_offset!(byte_offset)
  @parse_result.source.line(byte_offset)
end

#line_end(byte_offset) ⇒ Object



86
87
88
89
90
91
92
# File 'lib/astel/source_file.rb', line 86

def line_end(byte_offset)
  validate_offset!(byte_offset)
  ending = @parse_result.source.line_end(byte_offset)
  ending -= 1 if ending.positive? && source.getbyte(ending - 1) == 10
  ending -= 1 if ending.positive? && source.getbyte(ending - 1) == 13
  ending
end

#line_location(byte_offset) ⇒ Object



94
95
96
# File 'lib/astel/source_file.rb', line 94

def line_location(byte_offset)
  location(line_start(byte_offset), line_end(byte_offset))
end

#line_location_with_newline(byte_offset) ⇒ Object



98
99
100
101
# File 'lib/astel/source_file.rb', line 98

def line_location_with_newline(byte_offset)
  validate_offset!(byte_offset)
  location(line_start(byte_offset), @parse_result.source.line_end(byte_offset))
end

#line_start(byte_offset) ⇒ Object



81
82
83
84
# File 'lib/astel/source_file.rb', line 81

def line_start(byte_offset)
  validate_offset!(byte_offset)
  @parse_result.source.line_start(byte_offset)
end

#linesObject



31
32
33
# File 'lib/astel/source_file.rb', line 31

def lines
  @lines ||= source.lines(chomp: false).map!(&:freeze).freeze
end

#magic_comment?(name) ⇒ Boolean

Returns:

  • (Boolean)


55
56
57
58
59
60
61
# File 'lib/astel/source_file.rb', line 55

def magic_comment?(name)
  pattern = /\A#\s*#{Regexp.escape(name.to_s)}\s*:\s*true\b/
  lines.first(2).any? do |line|
    candidate = line.start_with?("\uFEFF") ? line.delete_prefix("\uFEFF") : line
    candidate.match?(pattern)
  end
end

#newlineObject



107
108
109
110
111
112
# File 'lib/astel/source_file.rb', line 107

def newline
  @newline ||= begin
    crlf = source.b.scan(/\r\n/).length
    crlf > source.count("\n") - crlf ? "\r\n" : "\n"
  end
end

#slice(location) ⇒ Object



103
104
105
# File 'lib/astel/source_file.rb', line 103

def slice(location)
  source.byteslice(location.start_offset, location.end_offset - location.start_offset)
end

#valid?Boolean

Returns:

  • (Boolean)


35
36
37
# File 'lib/astel/source_file.rb', line 35

def valid?
  errors.empty?
end