diff --git a/tests/test_lineno.py b/tests/test_lineno.py new file mode 100644 index 00000000..d2116125 --- /dev/null +++ b/tests/test_lineno.py @@ -0,0 +1,89 @@ +import pytest + +from thriftpy2.parser import load + + +def test_struct_field_linenos(): + """Test that struct fields have correct lineno information.""" + thrift = load('parser-cases/structs.thrift') + + assert hasattr(thrift.Person, '_field_linenos') + assert thrift.Person._field_linenos['name'] == 2 + assert thrift.Person._field_linenos['address'] == 3 + + assert hasattr(thrift.Email, '_field_linenos') + assert thrift.Email._field_linenos['subject'] == 11 + assert thrift.Email._field_linenos['content'] == 12 + assert thrift.Email._field_linenos['sender'] == 13 + assert thrift.Email._field_linenos['recver'] == 14 + assert thrift.Email._field_linenos['metadata'] == 15 + + assert hasattr(thrift.Dog, '_field_linenos') + assert thrift.Dog._field_linenos['name'] == 19 + assert thrift.Dog._field_linenos['age'] == 20 + assert thrift.Dog._field_linenos['nickname'] == 21 + + +def test_enum_field_linenos(): + """Test that enum values have correct lineno information.""" + thrift = load('parser-cases/enums.thrift') + + assert hasattr(thrift.Lang, '_field_linenos') + assert thrift.Lang._field_linenos['C'] == 2 + assert thrift.Lang._field_linenos['Go'] == 3 + assert thrift.Lang._field_linenos['Java'] == 4 + assert thrift.Lang._field_linenos['Javascript'] == 5 + assert thrift.Lang._field_linenos['PHP'] == 6 + assert thrift.Lang._field_linenos['Python'] == 7 + assert thrift.Lang._field_linenos['Ruby'] == 8 + + assert hasattr(thrift.Country, '_field_linenos') + assert thrift.Country._field_linenos['US'] == 13 + assert thrift.Country._field_linenos['UK'] == 14 + assert thrift.Country._field_linenos['CN'] == 15 + + assert hasattr(thrift.OS, '_field_linenos') + assert thrift.OS._field_linenos['OSX'] == 20 + assert thrift.OS._field_linenos['Win'] == 21 + assert thrift.OS._field_linenos['Linux'] == 22 + + +def test_service_function_linenos(): + """Test that service functions have correct lineno information.""" + thrift = load('parser-cases/service.thrift') + + assert hasattr(thrift.EmailService, '_field_linenos') + assert thrift.EmailService._field_linenos['ping'] == 17 + assert thrift.EmailService._field_linenos['send'] == 19 + assert thrift.EmailService._field_linenos['receive'] == 22 + assert thrift.EmailService._field_linenos['empty'] == 23 + + +def test_type_linenos(): + """Test that types have correct lineno information.""" + thrift = load('parser-cases/service.thrift') + + assert hasattr(thrift.User, '__thrift_lineno__') + assert thrift.User.__thrift_lineno__ == 1 + assert hasattr(thrift.User, '__thrift_file__') + + assert hasattr(thrift.NetworkError, '__thrift_lineno__') + assert thrift.NetworkError.__thrift_lineno__ == 11 + assert hasattr(thrift.NetworkError, '__thrift_file__') + + assert hasattr(thrift.EmailService, '__thrift_lineno__') + assert thrift.EmailService.__thrift_lineno__ == 16 + assert hasattr(thrift.EmailService, '__thrift_file__') + + +def test_include_file_path(): + """Test that included types have correct file path information.""" + thrift = load('parser-cases/include.thrift', include_dirs=[ + './parser-cases'], module_name='include_thrift') + + assert hasattr(thrift, '__thrift_file__') + assert 'include.thrift' in thrift.__thrift_file__ + + included_thrift = getattr(thrift, 'included') + assert hasattr(included_thrift, '__thrift_file__') + assert 'included.thrift' in included_thrift.__thrift_file__ diff --git a/thriftpy2/parser/parser.py b/thriftpy2/parser/parser.py index 92706ee0..ed0c3a6e 100644 --- a/thriftpy2/parser/parser.py +++ b/thriftpy2/parser/parser.py @@ -213,7 +213,7 @@ def p_typedef(p): def p_enum(p): # noqa '''enum : ENUM IDENTIFIER '{' enum_seq '}' type_annotations''' - val = _make_enum(p[2], p[4]) + val = _make_enum(p[2], p[4], lineno=p.lineno(2)) setattr(threadlocal.thrift_stack[-1], p[2], val) _add_thrift_meta('enums', val) @@ -230,9 +230,9 @@ def p_enum_item(p): | IDENTIFIER type_annotations |''' if len(p) == 5: - p[0] = [p[1], p[3]] + p[0] = [p[1], p[3], p.lineno(1)] elif len(p) == 3: - p[0] = [p[1], None] + p[0] = [p[1], None, p.lineno(1)] def p_struct(p): @@ -243,7 +243,7 @@ def p_struct(p): def p_seen_struct(p): '''seen_struct : STRUCT IDENTIFIER ''' - val = _make_empty_struct(p[2]) + val = _make_empty_struct(p[2], lineno=p.lineno(2)) setattr(threadlocal.thrift_stack[-1], p[2], val) p[0] = val @@ -256,14 +256,14 @@ def p_union(p): def p_seen_union(p): '''seen_union : UNION IDENTIFIER ''' - val = _make_empty_struct(p[2]) + val = _make_empty_struct(p[2], lineno=p.lineno(2)) setattr(threadlocal.thrift_stack[-1], p[2], val) p[0] = val def p_exception(p): '''exception : EXCEPTION IDENTIFIER '{' field_seq '}' type_annotations ''' - val = _make_struct(p[2], p[4], base_cls=TException) + val = _make_struct(p[2], p[4], base_cls=TException, lineno=p.lineno(2)) setattr(threadlocal.thrift_stack[-1], p[2], val) _add_thrift_meta('exceptions', val) @@ -290,7 +290,7 @@ def p_simple_service(p): else: extends = None - val = _make_service(p[2], p[len(p) - 2], extends) + val = _make_service(p[2], p[len(p) - 2], extends, lineno=p.lineno(2)) setattr(thrift, p[2], val) _add_thrift_meta('services', val) @@ -318,7 +318,7 @@ def p_simple_function(p): else: throws = p[len(p) - 1] - p[0] = [oneway, p[base + 1], p[base + 2], p[base + 4], throws] + p[0] = [oneway, p[base + 1], p[base + 2], p[base + 4], throws, p.lineno(base + 2)] def p_function(p): @@ -370,7 +370,7 @@ def p_simple_field(p): else: val = None - p[0] = [p[1], p[2], p[3], p[4], val] + p[0] = [p[1], p[2], p[3], p[4], val, p.lineno(4)] def p_field(p): @@ -860,16 +860,21 @@ def __cast_struct(v): return __cast_struct -def _make_enum(name, kvs): +def _make_enum(name, kvs, lineno=None): + thrift = threadlocal.thrift_stack[-1] attrs = { - '__module__': threadlocal.thrift_stack[-1].__name__, - '_ttype': TType.I32 + '__module__': thrift.__name__, + '_ttype': TType.I32, + '__thrift_lineno__': lineno, + '__thrift_file__': getattr(thrift, '__thrift_file__', None) } cls = type(name, (object, ), attrs) _values_to_names = {} _names_to_values = {} + _field_linenos = {} + if kvs: val = kvs[0][1] if val is None: @@ -878,19 +883,24 @@ def _make_enum(name, kvs): if item[1] is None: item[1] = val + 1 val = item[1] - for key, val in kvs: + for key, val, field_lineno in kvs: setattr(cls, key, val) _values_to_names[val] = key _names_to_values[key] = val + _field_linenos[key] = field_lineno setattr(cls, '_VALUES_TO_NAMES', _values_to_names) setattr(cls, '_NAMES_TO_VALUES', _names_to_values) + setattr(cls, '_field_linenos', _field_linenos) return cls -def _make_empty_struct(name, ttype=TType.STRUCT, base_cls=TPayload): +def _make_empty_struct(name, ttype=TType.STRUCT, base_cls=TPayload, lineno=None): + thrift = threadlocal.thrift_stack[-1] attrs = { - '__module__': threadlocal.thrift_stack[-1].__name__, - '_ttype': ttype + '__module__': thrift.__name__, + '_ttype': ttype, + '__thrift_lineno__': lineno, + '__thrift_file__': getattr(thrift, '__thrift_file__', None) } return type(name, (base_cls, ), attrs) @@ -899,6 +909,7 @@ def _fill_in_struct(cls, fields, _gen_init=True): thrift_spec = {} default_spec = [] _tspec = {} + _field_linenos = {} for field in fields: if field[0] in thrift_spec or field[3] in _tspec: @@ -909,27 +920,35 @@ def _fill_in_struct(cls, fields, _gen_init=True): thrift_spec[field[0]] = _ttype_spec(ttype, field[3], field[1]) default_spec.append((field[3], field[4])) _tspec[field[3]] = field[1], ttype + _field_linenos[field[3]] = field[5] # lineno setattr(cls, 'thrift_spec', thrift_spec) setattr(cls, 'default_spec', default_spec) setattr(cls, '_tspec', _tspec) + setattr(cls, '_field_linenos', _field_linenos) if _gen_init: gen_init(cls, thrift_spec, default_spec) return cls def _make_struct(name, fields, ttype=TType.STRUCT, base_cls=TPayload, - _gen_init=True): - cls = _make_empty_struct(name, ttype=ttype, base_cls=base_cls) + _gen_init=True, lineno=None): + cls = _make_empty_struct(name, ttype=ttype, base_cls=base_cls, lineno=lineno) return _fill_in_struct(cls, fields, _gen_init=_gen_init) -def _make_service(name, funcs, extends): +def _make_service(name, funcs, extends, lineno=None): if extends is None: extends = object - attrs = {'__module__': threadlocal.thrift_stack[-1].__name__} + thrift = threadlocal.thrift_stack[-1] + attrs = { + '__module__': thrift.__name__, + '__thrift_lineno__': lineno, + '__thrift_file__': getattr(thrift, '__thrift_file__', None) + } cls = type(name, (extends, ), attrs) thrift_services = [] + _field_linenos = {} for func in funcs: func_name = func[2] @@ -956,9 +975,11 @@ def _make_service(name, funcs, extends): gen_init(result_cls, result_cls.thrift_spec, result_cls.default_spec) setattr(cls, result_name, result_cls) thrift_services.append(func_name) + _field_linenos[func_name] = func[5] # function lineno if extends is not None and hasattr(extends, 'thrift_services'): thrift_services.extend(extends.thrift_services) setattr(cls, 'thrift_services', thrift_services) + setattr(cls, '_field_linenos', _field_linenos) return cls