Skip to content

Commit

Permalink
Merge pull request #1 from confact/update-perf
Browse files Browse the repository at this point in the history
optimizing and added missing methods
  • Loading branch information
confact committed Sep 9, 2023
2 parents beae166 + f8ddcdb commit b077bb1
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 94 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,35 @@ sourcemap.parsed_mappings
It will return an array of Sourcemap::Mapping. Those mappings can be used to get the original source code and the original line and column.


Retrive all mappings for a line and column:

```crystal
sourcemap = Sourcemap::Parser.from_file("sourcemap.js.map")
mapping = sourcemap.mapping_for(1, 1)
```

Get mappings for specific source file:

```crystal
sourcemap = Sourcemap::Parser.from_file("sourcemap.js.map")
sourcemap.mappings_for_source("sourcemap.js")
```


## Development

clone the repo and see the code and test the specs. the specs can be run with `crystal spec`

## Contributing

1. Fork it (<https://github.com/your-github-user/sourcemap_parser/fork>)
1. Fork it (<https://github.com/confact/sourcemap/fork>)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

## Contributors

- [Håkan](https://github.com/your-github-user) - creator and maintainer
- [Håkan](https://github.com/confact) - creator and maintainer
8 changes: 3 additions & 5 deletions spec/sourcemap_parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ describe SourceMap::Parser do
it "find mapping for error" do
function = "t"
line = 1
column = 3698
column = 3697

sourcemap_file = File.read("spec/support/fixtures/members-51db324e3c861052.js.map")

sourcemap = SourceMap::Parser.from_string(sourcemap_file)

mapping = sourcemap.parsed_mappings.not_nil!.find do |map|
next if map.source_path.try(&.blank?)
map.generated_column == (column || 1) - 1 && map.generated_line == line
end
mapping = sourcemap.mapping_for(line, column)

mapping.should_not be_nil
mapping.should be_a SourceMap::Mapping
Expand All @@ -22,5 +19,6 @@ describe SourceMap::Parser do
mapping.not_nil!.source_line.should eq(19)
mapping.not_nil!.source_column.should eq(73)
(mapping.not_nil!.source_content || "").should_not be_empty
mapping.not_nil!.source_line_content.should eq(" const currentUserRole = members?.find(member => member.id === session?.user.id)?.role")
end
end
1 change: 1 addition & 0 deletions src/sourcemap.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# TODO: Write documentation for `Sourcemap`

require "./sourcemap_parser/*"

module Sourcemap
VERSION = "0.1.0"

Expand Down
66 changes: 32 additions & 34 deletions src/sourcemap_parser/mapping.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,64 +8,62 @@ module SourceMap
getter source_path : String?
property source_content : String?

def initialize(generated_line, generated_column, source_path, source_line, source_column, name)
@generated_line = generated_line
@generated_column = generated_column
@source_line = source_line
@source_column = source_column
@name = name
@source_path = source_path
# Using default values to reduce the number of initializers
def initialize(
@generated_line : Int32,
@generated_column : Int32,
@source_path : String? = nil,
@source_line : Int32 = 0,
@source_column : Int32 = 0,
@name : String? = nil
)
end

def initialize(generated_line, generated_column, source_path, source_line, source_column)
@generated_line = generated_line
@generated_column = generated_column
@source_line = source_line
@source_column = source_column
@source_path = source_path
end

def initialize(generated_line, generated_column)
@generated_line = generated_line
@generated_column = generated_column
def source_line_content : String?
return nil if source_path.nil?
return nil if source_content.nil?
source_code_splitted[source_line - 1]?
end

def pre_context(context_line_no : Int32)
return nil if @source_content.nil?
start_no = context_line_no - 4
start_no = 0 if start_no < 0
end_no = context_line_no - 1
end_no = source_code_length - 1 if end_no > source_code_length - 1
content = source_code_splitted
return nil if content.empty?
start_no = [0, context_line_no - 4].max
end_no = [content.size - 1, context_line_no - 1].min
source_code(start_no, end_no)
end

def post_context(context_line_no : Int32)
return nil if source_code_splitted.empty?
start_no = context_line_no + 1
start_no = 0 if start_no < 0
end_no = context_line_no + 4
end_no = source_code_length - 1 if end_no > source_code_length - 1
content = source_code_splitted
return nil if content.empty?
start_no = [0, context_line_no + 1].max
end_no = [content.size - 1, context_line_no + 4].min
source_code(start_no, end_no)
end

def context_line(context_line_no : Int32) : String?
return nil if source_code_splitted.empty?
source_code_splitted[context_line_no]
source_code_splitted[context_line_no]?
end

def source_code(from : Int32, to : Int32)
return nil if source_code_splitted.empty?
source_code_splitted[from..to]
end

def source_code_length
return 0 if @source_content.nil?
def source_code_length : Int32
source_code_splitted.size
end

def source_code_splitted : Array(String)
return [] of String if @source_content.nil?
@source_content.not_nil!.split("\n")
@split_content ||= (@source_content || "").split("\n")
end

# Returns true if the mapping is part of the app code
# so not from node_modules
def is_part_of_app? : Bool
return false if source_path.nil?
return false if source_path.start_with?("node_modules")
true
end
end
end
112 changes: 59 additions & 53 deletions src/sourcemap_parser/source_map.cr
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ module SourceMap
property sourcemap_uri : URI = URI.parse("")

def initialize(@version, @sources, @names, @file, @mappings)
parse_mappings(mappings || "")
end

def after_initialize
Expand All @@ -38,25 +37,13 @@ module SourceMap
from_json(string)
end

# All the numbers in SourceMaps are stored as differences from each other,
# so we need to remove the difference every time we read a number.
def undiff(int, type)
if previous[type]?
previous[type] += int
else
previous[type] = int
end
previous[type]
previous[type] = (previous[type]? || 0) + int
end

# Parse the mapping string from a SourceMap.
#
# The mappings string contains one comma-separated list of segments per line
# in the output file, these lists are joined by semi-colons.
#
def parse_mappings(string) : Array(Mapping)
previous = {} of String => Int32
@mappings.split(";").each_with_index do |line, line_idx|
previous.clear
string.split(";").each_with_index do |line, line_idx|
previous["generated_col"] = 0
line.split(",").each do |segment|
next if segment.empty?
Expand All @@ -66,62 +53,81 @@ module SourceMap
parsed_mappings.sort_by! { |x| [x.generated_line, x.generated_column] }
end

# Parse an individual mapping.
#
# This is a list of variable-length-quanitity, with 1, 4 or 5 items. See the spec
# https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
# for more details.
def parse_mapping(segment, line_num) : Mapping
item = VLQ.decode(segment)

unless [1, 4, 5].includes?(item.size)
raise Exception.new("In map for #{file}:#{line_num}: unparseable item: #{segment}")
end

map = if item.size == 4
Mapping.new(
line_num,
undiff(item[0], "generated_column"),
sources[undiff(item[1], "source_id")],
undiff(item[2], "source_line") + 1,
undiff(item[3], "source_column")
)
elsif item.size == 5
Mapping.new(
line_num,
undiff(item[0], "generated_column"),
sources[undiff(item[1], "source_id")],
undiff(item[2], "source_line") + 1,
undiff(item[3], "source_column"),
names[undiff(item[4], "name_id")]
)
else
Mapping.new(line_num, undiff(item[0], "generated_column"))
end
case item.size
when 4
source_idx = undiff(item[1], "source_id")
map = Mapping.new(
line_num,
undiff(item[0], "generated_column"),
sources[source_idx],
undiff(item[2], "source_line") + 1,
undiff(item[3], "source_column")
)
when 5
source_idx = undiff(item[1], "source_id")
name_idx = undiff(item[4], "name_id")
map = Mapping.new(
line_num,
undiff(item[0], "generated_column"),
sources[source_idx],
undiff(item[2], "source_line") + 1,
undiff(item[3], "source_column"),
names[name_idx]
)
else
map = Mapping.new(line_num, undiff(item[0], "generated_column"))
end

if map.source_path
map.source_content = source_content_for(map.source_path.not_nil!)
end

if map.generated_column < 0
raise Exception.new("In map for #{file}:#{line_num}: unexpected generated_column: #{map.generated_column}")
elsif map.source_line < 1
raise Exception.new("In map for #{file}:#{line_num}: unexpected source_line: #{map.source_line}")
elsif map.source_column < 0
raise Exception.new("In map for #{file}:#{line_num}: unexpected source_column: #{map.source_column}")
end
raise Exception.new("In map for #{file}:#{line_num}: unexpected generated_column: #{map.generated_column}") if map.generated_column < 0
raise Exception.new("In map for #{file}:#{line_num}: unexpected source_line: #{map.source_line}") if map.source_line < 1
raise Exception.new("In map for #{file}:#{line_num}: unexpected source_column: #{map.source_column}") if map.source_column < 0

map
end

def source_content_for(source_id : String) : String?
return nil if sources_content.empty?
return nil if source_index_for(source_id).nil?
sources_content[source_index_for(source_id).not_nil!]?
# Retrieve a Mapping for a specific line and column in the generated code
def mapping_for(generated_line : Int32, generated_column : Int32) : Mapping?
parsed_mappings.find do |mapping|
mapping.generated_line == generated_line && mapping.generated_column == generated_column
end
end

def source_index_for(source_id : String) : Int32?
# Retrieve a mapping for a specific line in the generated code and with source_path
def mapping_with_source_path_for(generated_line : Int32, source_path : String) : Mapping?
parsed_mappings.find do |mapping|
next if mapping.source_path.blank?
mapping.generated_line == generated_line && mapping.generated_column == generated_column
end
end

def mapping_with_less_column_for(generated_line : Int32, generated_column : Int32, start_column : Int32 = 1) : Mapping?
mapping_with_source_path_for(generated_line, (generated_column || start_column) - 1)
end

# Retrieve all mappings for a specific source path
def mappings_for_source(source_path : String) : Array(Mapping)
parsed_mappings.select { |mapping| mapping.source_path == source_path }
end

private def source_content_for(source_id : String) : String?
idx = source_index_for(source_id)
idx ? sources_content[idx] : nil
end

private def source_index_for(source_id : String) : Int32?
sources.index(source_id)
end
end
end

0 comments on commit b077bb1

Please sign in to comment.