1# Copyright (c) Facebook, Inc. and its affiliates. 2# 3# This source code is licensed under the MIT license found in the 4# LICENSE file in the root directory of this source tree. 5 6import dataclasses 7from typing import Union 8 9from libcst._parser.detect_config import detect_config 10from libcst._parser.parso.utils import PythonVersionInfo 11from libcst._parser.types.config import ParserConfig, PartialParserConfig 12from libcst.testing.utils import UnitTest, data_provider 13 14 15class TestDetectConfig(UnitTest): 16 @data_provider( 17 { 18 "empty_input": { 19 "source": b"", 20 "partial": PartialParserConfig(python_version="3.7"), 21 "detect_trailing_newline": True, 22 "detect_default_newline": True, 23 "expected_config": ParserConfig( 24 lines=["\n", ""], 25 encoding="utf-8", 26 default_indent=" ", 27 default_newline="\n", 28 has_trailing_newline=False, 29 version=PythonVersionInfo(3, 7), 30 future_imports=frozenset(), 31 ), 32 }, 33 "detect_trailing_newline_disabled": { 34 "source": b"", 35 "partial": PartialParserConfig(python_version="3.7"), 36 "detect_trailing_newline": False, 37 "detect_default_newline": True, 38 "expected_config": ParserConfig( 39 lines=[""], # the trailing newline isn't inserted 40 encoding="utf-8", 41 default_indent=" ", 42 default_newline="\n", 43 has_trailing_newline=False, 44 version=PythonVersionInfo(3, 7), 45 future_imports=frozenset(), 46 ), 47 }, 48 "detect_default_newline_disabled": { 49 "source": b"pass\r", 50 "partial": PartialParserConfig(python_version="3.7"), 51 "detect_trailing_newline": False, 52 "detect_default_newline": False, 53 "expected_config": ParserConfig( 54 lines=["pass\r", ""], # the trailing newline isn't inserted 55 encoding="utf-8", 56 default_indent=" ", 57 default_newline="\n", 58 has_trailing_newline=False, 59 version=PythonVersionInfo(3, 7), 60 future_imports=frozenset(), 61 ), 62 }, 63 "newline_inferred": { 64 "source": b"first_line\r\n\nsomething\n", 65 "partial": PartialParserConfig(python_version="3.7"), 66 "detect_trailing_newline": True, 67 "detect_default_newline": True, 68 "expected_config": ParserConfig( 69 lines=["first_line\r\n", "\n", "something\n", ""], 70 encoding="utf-8", 71 default_indent=" ", 72 default_newline="\r\n", 73 has_trailing_newline=True, 74 version=PythonVersionInfo(3, 7), 75 future_imports=frozenset(), 76 ), 77 }, 78 "newline_partial_given": { 79 "source": b"first_line\r\nsecond_line\r\n", 80 "partial": PartialParserConfig( 81 default_newline="\n", python_version="3.7" 82 ), 83 "detect_trailing_newline": True, 84 "detect_default_newline": True, 85 "expected_config": ParserConfig( 86 lines=["first_line\r\n", "second_line\r\n", ""], 87 encoding="utf-8", 88 default_indent=" ", 89 default_newline="\n", # The given partial disables inference 90 has_trailing_newline=True, 91 version=PythonVersionInfo(3, 7), 92 future_imports=frozenset(), 93 ), 94 }, 95 "indent_inferred": { 96 "source": b"if test:\n\t something\n", 97 "partial": PartialParserConfig(python_version="3.7"), 98 "detect_trailing_newline": True, 99 "detect_default_newline": True, 100 "expected_config": ParserConfig( 101 lines=["if test:\n", "\t something\n", ""], 102 encoding="utf-8", 103 default_indent="\t ", 104 default_newline="\n", 105 has_trailing_newline=True, 106 version=PythonVersionInfo(3, 7), 107 future_imports=frozenset(), 108 ), 109 }, 110 "indent_partial_given": { 111 "source": b"if test:\n\t something\n", 112 "partial": PartialParserConfig( 113 default_indent=" ", python_version="3.7" 114 ), 115 "detect_trailing_newline": True, 116 "detect_default_newline": True, 117 "expected_config": ParserConfig( 118 lines=["if test:\n", "\t something\n", ""], 119 encoding="utf-8", 120 default_indent=" ", 121 default_newline="\n", 122 has_trailing_newline=True, 123 version=PythonVersionInfo(3, 7), 124 future_imports=frozenset(), 125 ), 126 }, 127 "encoding_inferred": { 128 "source": b"#!/usr/bin/python3\n# -*- coding: latin-1 -*-\npass\n", 129 "partial": PartialParserConfig(python_version="3.7"), 130 "detect_trailing_newline": True, 131 "detect_default_newline": True, 132 "expected_config": ParserConfig( 133 lines=[ 134 "#!/usr/bin/python3\n", 135 "# -*- coding: latin-1 -*-\n", 136 "pass\n", 137 "", 138 ], 139 encoding="iso-8859-1", # this is an alias for latin-1 140 default_indent=" ", 141 default_newline="\n", 142 has_trailing_newline=True, 143 version=PythonVersionInfo(3, 7), 144 future_imports=frozenset(), 145 ), 146 }, 147 "encoding_partial_given": { 148 "source": b"#!/usr/bin/python3\n# -*- coding: latin-1 -*-\npass\n", 149 "partial": PartialParserConfig( 150 encoding="us-ascii", python_version="3.7" 151 ), 152 "detect_trailing_newline": True, 153 "detect_default_newline": True, 154 "expected_config": ParserConfig( 155 lines=[ 156 "#!/usr/bin/python3\n", 157 "# -*- coding: latin-1 -*-\n", 158 "pass\n", 159 "", 160 ], 161 encoding="us-ascii", 162 default_indent=" ", 163 default_newline="\n", 164 has_trailing_newline=True, 165 version=PythonVersionInfo(3, 7), 166 future_imports=frozenset(), 167 ), 168 }, 169 "encoding_str_not_bytes_disables_inference": { 170 "source": "#!/usr/bin/python3\n# -*- coding: latin-1 -*-\npass\n", 171 "partial": PartialParserConfig(python_version="3.7"), 172 "detect_trailing_newline": True, 173 "detect_default_newline": True, 174 "expected_config": ParserConfig( 175 lines=[ 176 "#!/usr/bin/python3\n", 177 "# -*- coding: latin-1 -*-\n", 178 "pass\n", 179 "", 180 ], 181 encoding="utf-8", # because source is a str, don't infer latin-1 182 default_indent=" ", 183 default_newline="\n", 184 has_trailing_newline=True, 185 version=PythonVersionInfo(3, 7), 186 future_imports=frozenset(), 187 ), 188 }, 189 "encoding_non_ascii_compatible_utf_16_with_bom": { 190 "source": b"\xff\xfet\x00e\x00s\x00t\x00", 191 "partial": PartialParserConfig(encoding="utf-16", python_version="3.7"), 192 "detect_trailing_newline": True, 193 "detect_default_newline": True, 194 "expected_config": ParserConfig( 195 lines=["test\n", ""], 196 encoding="utf-16", 197 default_indent=" ", 198 default_newline="\n", 199 has_trailing_newline=False, 200 version=PythonVersionInfo(3, 7), 201 future_imports=frozenset(), 202 ), 203 }, 204 "detect_trailing_newline_missing_newline": { 205 "source": b"test", 206 "partial": PartialParserConfig(python_version="3.7"), 207 "detect_trailing_newline": True, 208 "detect_default_newline": True, 209 "expected_config": ParserConfig( 210 lines=["test\n", ""], 211 encoding="utf-8", 212 default_indent=" ", 213 default_newline="\n", 214 has_trailing_newline=False, 215 version=PythonVersionInfo(3, 7), 216 future_imports=frozenset(), 217 ), 218 }, 219 "detect_trailing_newline_has_newline": { 220 "source": b"test\n", 221 "partial": PartialParserConfig(python_version="3.7"), 222 "detect_trailing_newline": True, 223 "detect_default_newline": True, 224 "expected_config": ParserConfig( 225 lines=["test\n", ""], 226 encoding="utf-8", 227 default_indent=" ", 228 default_newline="\n", 229 has_trailing_newline=True, 230 version=PythonVersionInfo(3, 7), 231 future_imports=frozenset(), 232 ), 233 }, 234 "detect_trailing_newline_missing_newline_after_line_continuation": { 235 "source": b"test\\\n", 236 "partial": PartialParserConfig(python_version="3.7"), 237 "detect_trailing_newline": True, 238 "detect_default_newline": True, 239 "expected_config": ParserConfig( 240 lines=["test\\\n", "\n", ""], 241 encoding="utf-8", 242 default_indent=" ", 243 default_newline="\n", 244 has_trailing_newline=False, 245 version=PythonVersionInfo(3, 7), 246 future_imports=frozenset(), 247 ), 248 }, 249 "detect_trailing_newline_has_newline_after_line_continuation": { 250 "source": b"test\\\n\n", 251 "partial": PartialParserConfig(python_version="3.7"), 252 "detect_trailing_newline": True, 253 "detect_default_newline": True, 254 "expected_config": ParserConfig( 255 lines=["test\\\n", "\n", ""], 256 encoding="utf-8", 257 default_indent=" ", 258 default_newline="\n", 259 has_trailing_newline=True, 260 version=PythonVersionInfo(3, 7), 261 future_imports=frozenset(), 262 ), 263 }, 264 "future_imports_in_correct_position": { 265 "source": b"# C\n''' D '''\nfrom __future__ import a as b\n", 266 "partial": PartialParserConfig(python_version="3.7"), 267 "detect_trailing_newline": True, 268 "detect_default_newline": True, 269 "expected_config": ParserConfig( 270 lines=[ 271 "# C\n", 272 "''' D '''\n", 273 "from __future__ import a as b\n", 274 "", 275 ], 276 encoding="utf-8", 277 default_indent=" ", 278 default_newline="\n", 279 has_trailing_newline=True, 280 version=PythonVersionInfo(3, 7), 281 future_imports=frozenset({"a"}), 282 ), 283 }, 284 "future_imports_in_mixed_position": { 285 "source": ( 286 b"from __future__ import a, b\nimport os\n" 287 + b"from __future__ import c\n" 288 ), 289 "partial": PartialParserConfig(python_version="3.7"), 290 "detect_trailing_newline": True, 291 "detect_default_newline": True, 292 "expected_config": ParserConfig( 293 lines=[ 294 "from __future__ import a, b\n", 295 "import os\n", 296 "from __future__ import c\n", 297 "", 298 ], 299 encoding="utf-8", 300 default_indent=" ", 301 default_newline="\n", 302 has_trailing_newline=True, 303 version=PythonVersionInfo(3, 7), 304 future_imports=frozenset({"a", "b"}), 305 ), 306 }, 307 } 308 ) 309 def test_detect_module_config( 310 self, 311 *, 312 source: Union[str, bytes], 313 partial: PartialParserConfig, 314 detect_trailing_newline: bool, 315 detect_default_newline: bool, 316 expected_config: ParserConfig, 317 ) -> None: 318 self.assertEqual( 319 dataclasses.asdict( 320 detect_config( 321 source, 322 partial=partial, 323 detect_trailing_newline=detect_trailing_newline, 324 detect_default_newline=detect_default_newline, 325 ).config 326 ), 327 dataclasses.asdict(expected_config), 328 ) 329