Skip to content

Commit

Permalink
Tests for source maps helper classes.
Browse files Browse the repository at this point in the history
To run the tests do something like this in the root
    export pythonpath=c:/python34
    tests/runtests.sh

Fixes to source maps helper classes to make the tests pass, including
    * insensitive case comparison of file paths, URL paths, and paths in
      source maps where missing. as mentioned before this is nice for
      Windows but not ideal for OSX. a subsequent change should do this
      only for Windows.

    * some invalid source map cases, or missing authored file or map
      file, or empty generated file, or missing source map comment

    * failure when URL could not be mapped to file system
  • Loading branch information
danmoseley committed Aug 18, 2015
1 parent eca8610 commit 6da58fe
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 92 deletions.
18 changes: 11 additions & 7 deletions projectsystem/DocumentMapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

class Position:
def __init__(self, file_name, line, column):
if (line < 0 or column < 0):
raise ValueError("Invalid arguments")

# zero-based line and column values
self.__file_name = file_name
self.__line = line
Expand Down Expand Up @@ -74,7 +77,7 @@ def delete_mapping(file_name):

# Delete corresponding authored source mappings
for authored_source in mapping.get_authored_files():
MappingsManager.authored_file_mappings.pop(authored_source)
MappingsManager.authored_file_mappings.pop(authored_source.lower())

@staticmethod
def delete_all_mappings():
Expand All @@ -89,17 +92,18 @@ class MappingInfo:

def __init__(self, generated_file):
source_map_file = Sourcemap.get_sourcemap_file(generated_file)

if not source_map_file:
return

self.parsed_source_map = Sourcemap.ParsedSourceMap(source_map_file)
self.parsed_source_map = None
if len(source_map_file):
self.parsed_source_map = Sourcemap.ParsedSourceMap(source_map_file)
self.generated_file = generated_file

if self.parsed_source_map:
self.authored_sources = self.parsed_source_map.get_authored_sources_path()
self.line_mappings = self.parsed_source_map.line_mappings

def is_valid(self):
return len(self.line_mappings) > 0

def get_authored_files(self):
return self.authored_sources

Expand All @@ -123,6 +127,7 @@ def get_authored_position(self, zero_based_line, zero_based_column):
return Position(self.authored_sources[file_number], line_number, column_number)

def get_generated_position(self, authored_file_name, zero_based_line, zero_based_column):
authored_file_name = authored_file_name.lower()
if not authored_file_name in self.authored_sources or zero_based_line < 0 or zero_based_column < 0:
return None

Expand All @@ -143,4 +148,3 @@ def get_generated_position(self, authored_file_name, zero_based_line, zero_based
file_number = max(line_mappings[mapping_index].file_num, 0)

return Position(self.generated_file, line_number, column_number)

125 changes: 64 additions & 61 deletions projectsystem/Sourcemap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,78 +4,81 @@

def get_sourcemap_file(file_name):
sourcemap_prefix = "//# sourceMappingURL="
map_file = None

with open(file_name, "r") as f:
# Read the last line of the file containing sourcemap information
sourcemap_info = f.readlines()[-1]

index = sourcemap_info.find(sourcemap_prefix)

if index != -1:
map_file = sourcemap_info[index + len(sourcemap_prefix):].strip()
map_file = os.path.dirname(file_name) + os.path.sep + map_file
f.close()
map_file = ""
try:
with open(file_name, "r") as f:
# Read the last line of the file containing sourcemap information
sourcemap_info = f.readlines()[-1]
if (sourcemap_info and len(sourcemap_info) > 0 and sourcemap_info.index(sourcemap_prefix) is 0):
map_file = sourcemap_info[len(sourcemap_prefix):].strip()
map_file = os.path.dirname(file_name) + os.path.sep + map_file
f.close()
except:
pass

return map_file


class ParsedSourceMap:
def __init__(self, file_name):
with open(file_name, "r") as f:
self.content = json.loads(f.read())
f.close()

if self.content:
self.root_path = os.path.abspath(os.path.dirname(file_name) + os.path.sep + self.content["sourceRoot"])
self.version = self.content["version"]
self.authored_sources = self.content["sources"]
self.line_mappings = SourceMapParser.calculate_line_mappings(self.content)
try:
self.content = None
with open(file_name, "r") as f:
self.content = json.loads(f.read())
f.close()

if self.content:
self.root_path = os.path.abspath(os.path.dirname(file_name) + os.path.sep + self.content["sourceRoot"])
self.version = self.content["version"]
self.authored_sources = self.content["sources"]
self.line_mappings = SourceMapParser.calculate_line_mappings(self.content)
except:
pass

def get_authored_sources_path(self):
return [os.path.abspath(self.root_path + os.path.sep + x) for x in self.authored_sources]
return [os.path.abspath(self.root_path + os.path.sep + x).lower() for x in self.authored_sources] if self.content else []


class LineMapping:
# All line mappings are zero based
def __init__(self):
self.generated_line = 0
self.generated_column = 0
self.source_line = 0
self.source_column = 0
self.file_num = 0

@staticmethod
def compare_generated_mappings(mapping, line, column):
return (column - mapping.generated_column) if (mapping.generated_line == line) else line - mapping.generated_line

@staticmethod
def compare_source_mappings(mapping, line, column):
return (column - mapping.source_column) if (mapping.source_line == line) else line - mapping.source_line

@staticmethod
def binary_search(line_mappings, line, column, comparator):
max_index = len(line_mappings) - 1
min_index = 0

while (min_index <= max_index):
mid = (max_index + min_index) >> 1

comparison = comparator(line_mappings[mid], line, column)
if (comparison > 0):
min_index = mid + 1
elif (comparison < 0):
max_index = mid - 1
else:
max_index = mid
break

# Find the closest match
result = max(min(len(line_mappings) - 1, max_index), 0)
while (result + 1 < len(line_mappings) and comparator(line_mappings[result + 1], line, column) == 0):
result += 1

return result
# All line mappings are zero based
def __init__(self):
self.generated_line = 0
self.generated_column = 0
self.source_line = 0
self.source_column = 0
self.file_num = 0

@staticmethod
def compare_generated_mappings(mapping, line, column):
return (column - mapping.generated_column) if (mapping.generated_line == line) else line - mapping.generated_line

@staticmethod
def compare_source_mappings(mapping, line, column):
return (column - mapping.source_column) if (mapping.source_line == line) else line - mapping.source_line

@staticmethod
def binary_search(line_mappings, line, column, comparator):
max_index = len(line_mappings) - 1
min_index = 0

while (min_index <= max_index):
mid = (max_index + min_index) >> 1

comparison = comparator(line_mappings[mid], line, column)
if (comparison > 0):
min_index = mid + 1
elif (comparison < 0):
max_index = mid - 1
else:
max_index = mid
break

# Find the closest match
result = max(min(len(line_mappings) - 1, max_index), 0)
while (result + 1 < len(line_mappings) and comparator(line_mappings[result + 1], line, column) == 0):
result += 1

return result


class SourceMapParser:
Expand Down
19 changes: 9 additions & 10 deletions swi.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,9 @@ def scriptParsed(self, data, notification):
if len(files) > 0 and files[0] != '':
file_name = files[0]

# Create a file mapping to look for mapped source code
projectsystem.DocumentMapping.MappingsManager.create_mapping(file_name)
if (file_name):
# Create a file mapping to look for mapped source code
projectsystem.DocumentMapping.MappingsManager.create_mapping(file_name)

file_to_scriptId.append({'file': file_name, 'scriptId': str(scriptId), 'url': data['url']})
# don't try to match shorter fragments, we already found a match
Expand Down Expand Up @@ -516,7 +517,7 @@ def run(self):
if 'breakpointId' in breaks[row]:
channel.send(webkit.Debugger.removeBreakpoint(breaks[row]['breakpointId']))

del brk_object[file_name];
del brk_object[file_name.lower()];

save_breaks()
update_overlays()
Expand Down Expand Up @@ -675,7 +676,6 @@ def run(self, edit):
sel = view.sel()[0]
start = view.rowcol(sel.begin())
end = view.rowcol(sel.end())
print("Getting mapped code info for:", view_name, start, end)

mapped_start = file_mapping.get_generated_position(view_name, start[0], start[1]) \
if is_authored_file \
Expand Down Expand Up @@ -1228,13 +1228,13 @@ def del_breakpoint_by_full_path(file_name, line):
del breaks[line]

if len(breaks) == 0:
del brk_object[file_name]
del brk_object[file_name.lower()]

save_breaks()


def get_breakpoints_by_full_path(file_name):
return brk_object.get(file_name, None)
return brk_object.get(file_name.lower(), None)

def get_breakpoints_by_scriptId(scriptId):
file_name = find_script(str(scriptId))
Expand All @@ -1245,6 +1245,7 @@ def get_breakpoints_by_scriptId(scriptId):


def init_breakpoint_for_file(file_path):
file_path = file_path.lower()
if not file_path: # eg., mapping view
return
if not file_path in brk_object:
Expand Down Expand Up @@ -1336,10 +1337,8 @@ def set_selection(view, start_line, start_column, end_line, end_column):
def get_authored_position_if_necessary(file_name, line_number, column_number):
if is_source_map_enabled():
mapping = projectsystem.DocumentMapping.MappingsManager.get_mapping(file_name)
if mapping:
return mapping.get_authored_position(line_number, line_number)

return None
if mapping.is_valid():
return mapping.get_authored_position(line_number, column_number)

def is_source_map_enabled():
global source_map_state
Expand Down
Loading

0 comments on commit 6da58fe

Please sign in to comment.