Skip to content

Commit

Permalink
Make line numbers in errors moderately less wrong.
Browse files Browse the repository at this point in the history
  • Loading branch information
eevee committed Dec 10, 2014
1 parent be4f062 commit 77a5828
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 24 deletions.
3 changes: 3 additions & 0 deletions scss/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ def parse_children(self, scope=None):
for source_file in self.sources:
rule = SassRule(
source_file=source_file,
lineno=1,

unparsed_contents=source_file.contents,
namespace=root_namespace,
Expand Down Expand Up @@ -1163,6 +1164,7 @@ def _nest_at_rules(self, rule, scope, block):
source_file=rule.source_file,
import_key=rule.import_key,
lineno=block.lineno,
num_header_lines=block.header.num_lines,
unparsed_contents=block.unparsed_contents,

legacy_compiler_options=rule.legacy_compiler_options,
Expand Down Expand Up @@ -1204,6 +1206,7 @@ def _nest_rules(self, rule, scope, block):
source_file=rule.source_file,
import_key=rule.import_key,
lineno=block.lineno,
num_header_lines=block.header.num_lines,
unparsed_contents=block.unparsed_contents,

legacy_compiler_options=rule.legacy_compiler_options,
Expand Down
1 change: 0 additions & 1 deletion scss/cssdefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,6 @@ def unescape(string):
_escape_chars_re = re.compile(r'([^-a-zA-Z0-9_])')
_interpolate_re = re.compile(r'(#\{\s*)?(\$[-\w]+)(?(1)\s*\})')
_spaces_re = re.compile(r'\s+')
_expand_rules_space_re = re.compile(r'\s*{')
_collapse_properties_space_re = re.compile(r'([:#])\s*{')
_variable_re = re.compile('^\\$[-a-zA-Z0-9_]+$')

Expand Down
9 changes: 6 additions & 3 deletions scss/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ def format_sass_stack(self):
last_file = self.rule_stack[0].source_file

# TODO this could go away if rules knew their import chains...
# TODO mixins and the like here too?
# TODO the line number is wrong here, since this doesn't include the
# *block* being parsed!
# TODO this doesn't mention mixins or function calls. really need to
# track the call stack better. atm we skip other calls in the same
# file because most of them are just nesting, but they might not be!
# TODO the line number is wrong here for @imports, because we don't
# have access to the UnparsedBlock representing the import!
# TODO @content is completely broken; it's basically textual inclusion
for rule in self.rule_stack[1:]:
if rule.source_file is not last_file:
ret.extend((
Expand Down
24 changes: 17 additions & 7 deletions scss/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class SassRule(object):

def __init__(
self, source_file, import_key=None, unparsed_contents=None,
num_header_lines=0,
options=None, legacy_compiler_options=None, properties=None,
namespace=None,
lineno=0, extends_selectors=frozenset(),
Expand All @@ -48,6 +49,7 @@ def __init__(
self.import_key = import_key
self.lineno = lineno

self.num_header_lines = num_header_lines
self.unparsed_contents = unparsed_contents
self.legacy_compiler_options = legacy_compiler_options or {}
self.options = options or {}
Expand Down Expand Up @@ -211,6 +213,7 @@ class BlockHeader(object):

@classmethod
def parse(cls, prop, has_contents=False):
num_lines = prop.count('\n')
prop = prop.strip()

# Simple pre-processing
Expand Down Expand Up @@ -247,25 +250,27 @@ def parse(cls, prop, has_contents=False):
directive, argument = prop, None
directive = directive.lower()

return BlockAtRuleHeader(directive, argument)
return BlockAtRuleHeader(directive, argument, num_lines)
elif prop.split(None, 1)[0].endswith(':'):
# Syntax is "<scope>: [prop]" -- if the optional prop exists, it
# becomes the first rule with no suffix
scope, unscoped_value = prop.split(':', 1)
scope = scope.rstrip()
unscoped_value = unscoped_value.lstrip()
return BlockScopeHeader(scope, unscoped_value)
return BlockScopeHeader(scope, unscoped_value, num_lines)
else:
return BlockSelectorHeader(prop)
return BlockSelectorHeader(prop, num_lines)


class BlockAtRuleHeader(BlockHeader):
is_atrule = True

def __init__(self, directive, argument):
def __init__(self, directive, argument, num_lines=0):
self.directive = directive
self.argument = argument

self.num_lines = num_lines

def __repr__(self):
return "<%s %r %r>" % (type(self).__name__, self.directive, self.argument)

Expand All @@ -279,9 +284,11 @@ def render(self):
class BlockSelectorHeader(BlockHeader):
is_selector = True

def __init__(self, selectors):
def __init__(self, selectors, num_lines=0):
self.selectors = tuple(selectors)

self.num_lines = num_lines

def __repr__(self):
return "<%s %r>" % (type(self).__name__, self.selectors)

Expand All @@ -295,14 +302,16 @@ def render(self, sep=', ', super_selector=''):
class BlockScopeHeader(BlockHeader):
is_scope = True

def __init__(self, scope, unscoped_value):
def __init__(self, scope, unscoped_value, num_lines=0):
self.scope = scope

if unscoped_value:
self.unscoped_value = unscoped_value
else:
self.unscoped_value = None

self.num_lines = num_lines


class UnparsedBlock(object):
"""A Sass block whose contents have not yet been parsed.
Expand All @@ -329,7 +338,8 @@ def __init__(self, parent_rule, lineno, prop, unparsed_contents):
self.header = BlockHeader.parse(prop, has_contents=bool(unparsed_contents))

# Basic properties
self.lineno = lineno
self.lineno = (
parent_rule.lineno - parent_rule.num_header_lines + lineno - 1)
self.prop = prop
self.unparsed_contents = unparsed_contents

Expand Down
23 changes: 10 additions & 13 deletions scss/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from scss.cssdefs import (
_ml_comment_re, _sl_comment_re,
_expand_rules_space_re, _collapse_properties_space_re,
_collapse_properties_space_re,
_strings_re,
)
from scss.cssdefs import determine_encoding
Expand Down Expand Up @@ -261,7 +261,7 @@ def parse_scss_line(self, line, state):
if line is None:
line = ''

line = state['line_buffer'] + line.rstrip() # remove EOL character
line = state['line_buffer'] + line

if line and line[-1] == '\\':
state['line_buffer'] = line[:-1]
Expand All @@ -274,10 +274,8 @@ def parse_scss_line(self, line, state):

state['prev_line'] = line

if output:
output += '\n'
ret += output

ret += output
ret += '\n'
return ret

def parse_sass_line(self, line, state):
Expand All @@ -286,7 +284,7 @@ def parse_sass_line(self, line, state):
if line is None:
line = ''

line = state['line_buffer'] + line.rstrip() # remove EOL character
line = state['line_buffer'] + line

if line and line[-1] == '\\':
state['line_buffer'] = line[:-1]
Expand Down Expand Up @@ -327,9 +325,8 @@ def parse_sass_line(self, line, state):
state['prev_indent'] = indent
state['prev_line'] = line

if output:
output += '\n'
ret += output
ret += output
ret += '\n'
return ret

def prepare_source(self, codestr, sass=False):
Expand All @@ -351,6 +348,9 @@ def prepare_source(self, codestr, sass=False):
# parse the last line stored in prev_line buffer
codestr += parse_line(None, state)

# pop off the extra \n parse_line puts at the beginning
codestr = codestr[1:]

# protects codestr: "..." strings
codestr = _strings_re.sub(
lambda m: _reverse_safe_strings_re.sub(
Expand All @@ -366,9 +366,6 @@ def prepare_source(self, codestr, sass=False):
codestr = _safe_strings_re.sub(
lambda m: _safe_strings[m.group(0)], codestr)

# expand the space in rules
codestr = _expand_rules_space_re.sub(' {', codestr)

# collapse the space in properties blocks
codestr = _collapse_properties_space_re.sub(r'\1{', codestr)

Expand Down

0 comments on commit 77a5828

Please sign in to comment.