def __post_init__(self) -> None: raw_python_version = self.python_version # `parse_version_string` will raise a ValueError if the version is invalid. # # We use object.__setattr__ because the dataclass is frozen. See: # https://docs.python.org/3/library/dataclasses.html#frozen-instances # This should be safe behavior inside of `__post_init__`. parsed_python_version = parse_version_string(None if isinstance( raw_python_version, AutoConfig) else raw_python_version) # Once we add support for more versions of Python, we can change this to detect # the supported version range. if parsed_python_version not in ( PythonVersionInfo(3, 5), PythonVersionInfo(3, 6), PythonVersionInfo(3, 7), PythonVersionInfo(3, 8), ): raise ValueError( "LibCST can only parse code using one of the following versions of " + "Python's grammar: 3.5, 3.6, 3.7, 3.8. More versions may be " + "supported by future releases.") object.__setattr__(self, "parsed_python_version", parsed_python_version) encoding = self.encoding if not isinstance(encoding, AutoConfig): try: codecs.lookup(encoding) except LookupError: raise ValueError( f"{repr(encoding)} is not a supported encoding") newline = self.default_newline if (not isinstance(newline, AutoConfig) and NEWLINE_RE.fullmatch(newline) is None): raise ValueError( f"Got an invalid value for default_newline: {repr(newline)}") indent = self.default_indent if not isinstance(indent, AutoConfig) and _INDENT_RE.fullmatch(indent) is None: raise ValueError( f"Got an invalid value for default_indent: {repr(indent)}")
def test_pick_compatible(self) -> None: self.assertEqual(PythonVersionInfo(3, 1), _pick_compatible_python_version("3.2")) self.assertEqual(PythonVersionInfo(3, 1), _pick_compatible_python_version("3.1")) self.assertEqual(PythonVersionInfo(3, 8), _pick_compatible_python_version("3.9")) self.assertEqual(PythonVersionInfo(3, 8), _pick_compatible_python_version("3.10")) self.assertEqual(PythonVersionInfo(3, 8), _pick_compatible_python_version("4.0")) with self.assertRaisesRegex( ValueError, (r"No version found older than 1\.0 \(PythonVersionInfo\(" + r"major=1, minor=0\)\) while running on"), ): _pick_compatible_python_version("1.0")
def tokenize_lines( # noqa: C901 lines: Iterable[str], version_info: PythonVersionInfo, start_pos: Tuple[int, int] = (1, 0), ) -> Generator[PythonToken, None, None]: token_collection = _get_token_collection(version_info) if version_info >= PythonVersionInfo(3, 7): return _tokenize_lines_py37_or_above( lines, version_info, token_collection, start_pos=start_pos ) else: return _tokenize_lines_py36_or_below( lines, version_info, token_collection, start_pos=start_pos )
class VersionCompareTest(UnitTest): @data_provider(( # Simple equality ("==3.6", PythonVersionInfo(3, 6), True), ("!=3.6", PythonVersionInfo(3, 6), False), # Equal or GT/LT (">=3.6", PythonVersionInfo(3, 5), False), (">=3.6", PythonVersionInfo(3, 6), True), (">=3.6", PythonVersionInfo(3, 7), True), ("<=3.6", PythonVersionInfo(3, 5), True), ("<=3.6", PythonVersionInfo(3, 6), True), ("<=3.6", PythonVersionInfo(3, 7), False), # GT/LT (">3.6", PythonVersionInfo(3, 5), False), (">3.6", PythonVersionInfo(3, 6), False), (">3.6", PythonVersionInfo(3, 7), True), ("<3.6", PythonVersionInfo(3, 5), True), ("<3.6", PythonVersionInfo(3, 6), False), ("<3.6", PythonVersionInfo(3, 7), False), # Multiple checks (">3.6,<3.8", PythonVersionInfo(3, 6), False), (">3.6,<3.8", PythonVersionInfo(3, 7), True), (">3.6,<3.8", PythonVersionInfo(3, 8), False), )) def test_tokenize( self, requested_version: str, actual_version: PythonVersionInfo, expected_result: bool, ) -> None: self.assertEqual(_should_include(requested_version, actual_version), expected_result)
class TestDetectConfig(UnitTest): @data_provider({ "empty_input": { "source": b"", "partial": PartialParserConfig(python_version="3.7"), "detect_trailing_newline": True, "detect_default_newline": True, "expected_config": ParserConfig( lines=["\n", ""], encoding="utf-8", default_indent=" ", default_newline="\n", has_trailing_newline=False, version=PythonVersionInfo(3, 7), ), }, "detect_trailing_newline_disabled": { "source": b"", "partial": PartialParserConfig(python_version="3.7"), "detect_trailing_newline": False, "detect_default_newline": True, "expected_config": ParserConfig( lines=[""], # the trailing newline isn't inserted encoding="utf-8", default_indent=" ", default_newline="\n", has_trailing_newline=False, version=PythonVersionInfo(3, 7), ), }, "detect_default_newline_disabled": { "source": b"pass\r", "partial": PartialParserConfig(python_version="3.7"), "detect_trailing_newline": False, "detect_default_newline": False, "expected_config": ParserConfig( lines=["pass\r", ""], # the trailing newline isn't inserted encoding="utf-8", default_indent=" ", default_newline="\n", has_trailing_newline=False, version=PythonVersionInfo(3, 7), ), }, "newline_inferred": { "source": b"first_line\r\n\nsomething\n", "partial": PartialParserConfig(python_version="3.7"), "detect_trailing_newline": True, "detect_default_newline": True, "expected_config": ParserConfig( lines=["first_line\r\n", "\n", "something\n", ""], encoding="utf-8", default_indent=" ", default_newline="\r\n", has_trailing_newline=True, version=PythonVersionInfo(3, 7), ), }, "newline_partial_given": { "source": b"first_line\r\nsecond_line\r\n", "partial": PartialParserConfig(default_newline="\n", python_version="3.7"), "detect_trailing_newline": True, "detect_default_newline": True, "expected_config": ParserConfig( lines=["first_line\r\n", "second_line\r\n", ""], encoding="utf-8", default_indent=" ", default_newline="\n", # The given partial disables inference has_trailing_newline=True, version=PythonVersionInfo(3, 7), ), }, "indent_inferred": { "source": b"if test:\n\t something\n", "partial": PartialParserConfig(python_version="3.7"), "detect_trailing_newline": True, "detect_default_newline": True, "expected_config": ParserConfig( lines=["if test:\n", "\t something\n", ""], encoding="utf-8", default_indent="\t ", default_newline="\n", has_trailing_newline=True, version=PythonVersionInfo(3, 7), ), }, "indent_partial_given": { "source": b"if test:\n\t something\n", "partial": PartialParserConfig(default_indent=" ", python_version="3.7"), "detect_trailing_newline": True, "detect_default_newline": True, "expected_config": ParserConfig( lines=["if test:\n", "\t something\n", ""], encoding="utf-8", default_indent=" ", default_newline="\n", has_trailing_newline=True, version=PythonVersionInfo(3, 7), ), }, "encoding_inferred": { "source": b"#!/usr/bin/python3\n# -*- coding: latin-1 -*-\npass\n", "partial": PartialParserConfig(python_version="3.7"), "detect_trailing_newline": True, "detect_default_newline": True, "expected_config": ParserConfig( lines=[ "#!/usr/bin/python3\n", "# -*- coding: latin-1 -*-\n", "pass\n", "", ], encoding="iso-8859-1", # this is an alias for latin-1 default_indent=" ", default_newline="\n", has_trailing_newline=True, version=PythonVersionInfo(3, 7), ), }, "encoding_partial_given": { "source": b"#!/usr/bin/python3\n# -*- coding: latin-1 -*-\npass\n", "partial": PartialParserConfig(encoding="us-ascii", python_version="3.7"), "detect_trailing_newline": True, "detect_default_newline": True, "expected_config": ParserConfig( lines=[ "#!/usr/bin/python3\n", "# -*- coding: latin-1 -*-\n", "pass\n", "", ], encoding="us-ascii", default_indent=" ", default_newline="\n", has_trailing_newline=True, version=PythonVersionInfo(3, 7), ), }, "encoding_str_not_bytes_disables_inference": { "source": "#!/usr/bin/python3\n# -*- coding: latin-1 -*-\npass\n", "partial": PartialParserConfig(python_version="3.7"), "detect_trailing_newline": True, "detect_default_newline": True, "expected_config": ParserConfig( lines=[ "#!/usr/bin/python3\n", "# -*- coding: latin-1 -*-\n", "pass\n", "", ], encoding="utf-8", # because source is a str, don't infer latin-1 default_indent=" ", default_newline="\n", has_trailing_newline=True, version=PythonVersionInfo(3, 7), ), }, "encoding_non_ascii_compatible_utf_16_with_bom": { "source": b"\xff\xfet\x00e\x00s\x00t\x00", "partial": PartialParserConfig(encoding="utf-16", python_version="3.7"), "detect_trailing_newline": True, "detect_default_newline": True, "expected_config": ParserConfig( lines=["test\n", ""], encoding="utf-16", default_indent=" ", default_newline="\n", has_trailing_newline=False, version=PythonVersionInfo(3, 7), ), }, }) def test_detect_module_config( self, *, source: Union[str, bytes], partial: PartialParserConfig, detect_trailing_newline: bool, detect_default_newline: bool, expected_config: ParserConfig, ) -> None: self.assertEqual( detect_config( source, partial=partial, detect_trailing_newline=detect_trailing_newline, detect_default_newline=detect_default_newline, ).config, expected_config, )