diff --git a/.gitignore b/.gitignore index be1d863..334beab 100644 --- a/.gitignore +++ b/.gitignore @@ -100,7 +100,6 @@ ipython_config.py # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock -poetry.toml # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. @@ -163,3 +162,4 @@ cython_debug/ #.idea/ tests/results/*.failed +.vscode/ diff --git a/dora/__main__.py b/dora/__main__.py new file mode 100644 index 0000000..3cf73a6 --- /dev/null +++ b/dora/__main__.py @@ -0,0 +1,42 @@ +"""Dora CLI.""" + +import argparse +import os +import sys + +from mypy.errors import CompileError + +from dora.search import search + + +def main() -> None: + """CLI entry point.""" + parser = argparse.ArgumentParser(description='Search source files by type expressions.') + parser.add_argument( + '-t', + '--type-expression', + metavar='', + help='The type expression to search for. If not provided, all types in the file will be listed.', + ) + parser.add_argument( + 'paths', + metavar='paths', + nargs='+', + help='The source files to search in.', + ) + args = parser.parse_args() + + for path in args.paths: + if not os.path.exists(path): + parser.error(f'The path "{path}" does not exist.') + + try: + for search_result in search(args.paths, args.type_expression): + print(search_result, end='\n\n') + except CompileError as e: + print(e, file=sys.stderr) + exit(1) + + +if __name__ == '__main__': + main() diff --git a/dora/main.py b/dora/main.py deleted file mode 100644 index 0e5e609..0000000 --- a/dora/main.py +++ /dev/null @@ -1,76 +0,0 @@ -from pathlib import Path -import sys -from typing import Iterable - -from mypy.build import BuildSource, build -from mypy.nodes import Expression -from mypy.options import Options - -HELP = """ -Usage: dora [] - -Args: - file: The Python file to analyze - type_expression: The type expression to search for. If not provided, all types - in the file will be listed. -""".strip() - - -def _extract_expr_str(file_content: str, expr: Expression) -> str: - lines = file_content.splitlines() - start = expr.line - 1 - end = expr.end_line - if end is None: - end = expr.line - - return '\n'.join(lines[start:end]) - - -def search_file(file: Path, type_expression: str | None) -> Iterable[str]: - content = file.read_text() - - options = Options() - options.export_types = True - result = build( - sources=[BuildSource(str(file), None)], - options=options, - ) - - file_types = result.graph['__main__'].manager.all_types - for expr, ty in file_types.items(): - if type_expression is None: - expr_str = _extract_expr_str(content, expr) - yield f'{file}:{expr.line}:{expr.column}\n Expr: {expr_str}\n Type: {ty}' - elif str(ty) == type_expression: - expr_str = _extract_expr_str(content, expr) - yield f'{file}:{expr.line}:{expr.column} {expr_str}' - - -def main() -> None: - args = sys.argv[1:] - if len(args) == 1: - file, type_expression = args[0], None - elif len(args) == 2: - file, type_expression = args - else: - print(HELP) - sys.exit(1) - - file = Path(file) - if not file.exists(): - print(f'File {file} does not exist') - sys.exit(1) - - if file.is_dir(): - print('dir', file) - for f in file.rglob('*.py'): - print(f) - for line in search_file(f, type_expression): - print(line) - elif file.is_file(): - for line in search_file(file, type_expression): - print(line) - - -if __name__ == '__main__': - main() diff --git a/dora/search.py b/dora/search.py new file mode 100644 index 0000000..1d399a8 --- /dev/null +++ b/dora/search.py @@ -0,0 +1,205 @@ +"""Search engine.""" + +from random import random +from typing import Any, Generator, Iterable + +from mypy.build import BuildManager, BuildResult, BuildSource, build +from mypy.find_sources import create_source_list +from mypy.nodes import MypyFile, Node +from mypy.options import Options +from mypy.plugin import Plugin, ReportConfigContext +from mypy.traverser import TraverserVisitor + + +class DoraPlugin(Plugin): + """Plugin to force mypy revalidate source files. + + Inspired by MypycPlugin from mypyc. + """ + + def __init__(self, sources: list[BuildSource], options: Options) -> None: + """Initialize the plugin. + + Args: + sources: The build sources whose cache should be invalidated. + options: The mypy options + """ + super().__init__(options) + self._sources = {source.path for source in sources} + + def report_config_data(self, ctx: ReportConfigContext) -> int | None: + """Force revalidation of the source file. + + Args: + ctx: The report configuration context. + + Returns: + A random number to force revalidation of the source file. + """ + if ctx.path in self._sources: + return random() # noqa: S311 + + return None + + +class SearchResult: + """Occurrence of a type expression in a source file.""" + + def __init__(self, mypy_file: MypyFile, node: Node, type_expression: str) -> None: + """Initialize the search result. + + Args: + mypy_file: The source file where the type expression was found. + node: The node where the type expression was found. + type_expression: The type expression that was found. + """ + self.mypy_file = mypy_file + self.node = node + self.type_expression = type_expression + + def __str__(self) -> str: + """Render the search result as a string. + + Returns: + A string representation of the search result. + """ + node_type = self.node.__class__.__name__ + node_text = self._extract_node_text(self.mypy_file.path, self.node) + column_pointer_offset = ' ' * self.node.column + result_text = f'{self.mypy_file.path}:{self.node.line}:{self.node.column}\n' + result_text += f'{column_pointer_offset}{self.type_expression} ({node_type})\n' + result_text += f'{column_pointer_offset}v\n' + result_text += node_text + return result_text + + @classmethod + def _extract_node_text(cls, path: str, node: Node) -> str: + """Extract the text of a node from the source file. + + Args: + path: The path to the source file. + node: The node with location context. + + Returns: + Node occurrence in the file. + """ + # probably would be to slow + # we can probably provide file content as an argument + with open(path, 'r') as f: + lines = f.readlines() + + line = node.line - 1 + end_line = node.end_line or node.line + lines = lines[line:end_line] + return ''.join(lines) + + +def search(paths: list[str], type_expression: str | None) -> Iterable[SearchResult]: + """Search for a type expression in a source file. + + Args: + paths: The source files to search in. + type_expression: The type expression to search for. + + Returns: + Found occurrences of the type expression. + """ + options = Options() + options.export_types = True + options.preserve_asts = True + + sources = create_source_list(paths, options) + + build_result = build( + sources=sources, + options=options, + extra_plugins=[DoraPlugin(sources, options)], + ) + return _search(sources, type_expression, build_result) + + +def _search( + sources: list[BuildSource], + type_expression: str | None, + build_result: BuildResult, +) -> Generator[SearchResult, None, None]: + """Search for a type expression in a source file. + + Args: + sources: The source files to search in. + type_expression: The type expression to search for. + build_result: The build result obtained from mypy.build.build(). + + Yields: + Found occurrences of the type expression. + """ + for bs in sources: + state = build_result.graph.get(bs.module) + if state is None: + continue + + if state.tree is None: + continue + + visitor = SearchVisitor(state.tree, type_expression, build_result.manager) + state.tree.accept(visitor) + yield from visitor.search_results + + +class SearchVisitor(TraverserVisitor): + """Performs a search for a type expression in a single ??? source file.""" + + def __init__( + self, + mypy_file: MypyFile, + type_expression: str | None, + manager: BuildManager, + ) -> None: + """Initialize the search visitor. + + Args: + mypy_file: Search source file and AST root. + type_expression: The type expression to search for. + manager: The mypy BuildManager obtained from mypy.build.build() result. + """ + super().__init__() + self.mypy_file = mypy_file + self.type_expression = type_expression + self.manager = manager + self.search_results: list[SearchResult] = [] + + def generic_visit(self, name: str, o: Node) -> None: + """Check type_expression against given node. + + Args: + name: Visitor name (visit_*: e.g. visit_var) + o: Target node. + + Returns: + Far traversing result. + """ + node_type = self.manager.all_types.get(o) + if node_type is not None: + if self.type_expression is None: + type_expression = str(node_type) + else: + type_expression = self.type_expression + + if str(node_type) == type_expression: + self.search_results.append(SearchResult(self.mypy_file, o, type_expression)) + + return super().__getattribute__(name)(o) + + def __getattribute__(self, name: str) -> Any: + """Mock behavior of all possible visit_* methods. + + Args: + name: Arg name. + + Returns: + Visit method mock if name visit_* method acquired. + """ + if name.startswith('visit_'): + return lambda o: self.generic_visit(name, o) + + return super().__getattribute__(name) diff --git a/poetry.lock b/poetry.lock index 3e0d50a..2516b9c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,354 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +[[package]] +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "bandit" +version = "1.7.9" +description = "Security oriented static analyser for python code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, + {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +PyYAML = ">=5.3.1" +rich = "*" +stevedore = ">=1.20.0" + +[package.extras] +baseline = ["GitPython (>=3.1.30)"] +sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] +toml = ["tomli (>=1.1.0)"] +yaml = ["PyYAML"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "darglint" +version = "1.8.1" +description = "A utility for ensuring Google-style docstrings stay up to date with the source code." +optional = false +python-versions = ">=3.6,<4.0" +files = [ + {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, + {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, +] + +[[package]] +name = "docutils" +version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "eradicate" +version = "2.3.0" +description = "Removes commented-out code." +optional = false +python-versions = "*" +files = [ + {file = "eradicate-2.3.0-py3-none-any.whl", hash = "sha256:2b29b3dd27171f209e4ddd8204b70c02f0682ae95eecb353f10e8d72b149c63e"}, + {file = "eradicate-2.3.0.tar.gz", hash = "sha256:06df115be3b87d0fc1c483db22a2ebb12bcf40585722810d809cc770f5031c37"}, +] + +[[package]] +name = "flake8" +version = "7.1.1" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.12.0,<2.13.0" +pyflakes = ">=3.2.0,<3.3.0" + +[[package]] +name = "flake8-bandit" +version = "4.1.1" +description = "Automated security testing with bandit and flake8." +optional = false +python-versions = ">=3.6" +files = [ + {file = "flake8_bandit-4.1.1-py3-none-any.whl", hash = "sha256:4c8a53eb48f23d4ef1e59293657181a3c989d0077c9952717e98a0eace43e06d"}, + {file = "flake8_bandit-4.1.1.tar.gz", hash = "sha256:068e09287189cbfd7f986e92605adea2067630b75380c6b5733dab7d87f9a84e"}, +] + +[package.dependencies] +bandit = ">=1.7.3" +flake8 = ">=5.0.0" + +[[package]] +name = "flake8-broken-line" +version = "1.0.0" +description = "Flake8 plugin to forbid backslashes for line breaks" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "flake8_broken_line-1.0.0-py3-none-any.whl", hash = "sha256:96c964336024a5030dc536a9f6fb02aa679e2d2a6b35b80a558b5136c35832a9"}, + {file = "flake8_broken_line-1.0.0.tar.gz", hash = "sha256:e2c6a17f8d9a129e99c1320fce89b33843e2963871025c4c2bb7b8b8d8732a85"}, +] + +[package.dependencies] +flake8 = ">5" + +[[package]] +name = "flake8-bugbear" +version = "24.4.26" +description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8_bugbear-24.4.26-py3-none-any.whl", hash = "sha256:cb430dd86bc821d79ccc0b030789a9c87a47a369667f12ba06e80f11305e8258"}, + {file = "flake8_bugbear-24.4.26.tar.gz", hash = "sha256:ff8d4ba5719019ebf98e754624c30c05cef0dadcf18a65d91c7567300e52a130"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +flake8 = ">=6.0.0" + +[package.extras] +dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", "tox"] + +[[package]] +name = "flake8-commas" +version = "4.0.0" +description = "Flake8 lint for trailing commas." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flake8_commas-4.0.0-py3-none-any.whl", hash = "sha256:cad476d71ba72e8b941a8508d5b9ffb6b03e50f7102982474f085ad0d674b685"}, + {file = "flake8_commas-4.0.0.tar.gz", hash = "sha256:a68834b42a9a31c94ca790efe557a932c0eae21a3479c6b9a23c4dc077e3ea96"}, +] + +[package.dependencies] +flake8 = ">=5" + +[[package]] +name = "flake8-comprehensions" +version = "3.15.0" +description = "A flake8 plugin to help you write better list/set/dict comprehensions." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flake8_comprehensions-3.15.0-py3-none-any.whl", hash = "sha256:b7e027bbb52be2ceb779ee12484cdeef52b0ad3c1fcb8846292bdb86d3034681"}, + {file = "flake8_comprehensions-3.15.0.tar.gz", hash = "sha256:923c22603e0310376a6b55b03efebdc09753c69f2d977755cba8bb73458a5d4d"}, +] + +[package.dependencies] +flake8 = ">=3,<3.2 || >3.2" + +[[package]] +name = "flake8-debugger" +version = "4.1.2" +description = "ipdb/pdb statement checker plugin for flake8" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flake8-debugger-4.1.2.tar.gz", hash = "sha256:52b002560941e36d9bf806fca2523dc7fb8560a295d5f1a6e15ac2ded7a73840"}, + {file = "flake8_debugger-4.1.2-py3-none-any.whl", hash = "sha256:0a5e55aeddcc81da631ad9c8c366e7318998f83ff00985a49e6b3ecf61e571bf"}, +] + +[package.dependencies] +flake8 = ">=3.0" +pycodestyle = "*" + +[[package]] +name = "flake8-docstrings" +version = "1.7.0" +description = "Extension for flake8 which uses pydocstyle to check docstrings" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, + {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, +] + +[package.dependencies] +flake8 = ">=3" +pydocstyle = ">=2.1" + +[[package]] +name = "flake8-eradicate" +version = "1.5.0" +description = "Flake8 plugin to find commented out code" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "flake8_eradicate-1.5.0-py3-none-any.whl", hash = "sha256:18acc922ad7de623f5247c7d5595da068525ec5437dd53b22ec2259b96ce9d22"}, + {file = "flake8_eradicate-1.5.0.tar.gz", hash = "sha256:aee636cb9ecb5594a7cd92d67ad73eb69909e5cc7bd81710cf9d00970f3983a6"}, +] + +[package.dependencies] +attrs = "*" +eradicate = ">=2.0,<3.0" +flake8 = ">5" + +[[package]] +name = "flake8-isort" +version = "6.1.1" +description = "flake8 plugin that integrates isort" +optional = false +python-versions = ">=3.8" +files = [ + {file = "flake8_isort-6.1.1-py3-none-any.whl", hash = "sha256:0fec4dc3a15aefbdbe4012e51d5531a2eb5fa8b981cdfbc882296a59b54ede12"}, + {file = "flake8_isort-6.1.1.tar.gz", hash = "sha256:c1f82f3cf06a80c13e1d09bfae460e9666255d5c780b859f19f8318d420370b3"}, +] + +[package.dependencies] +flake8 = "*" +isort = ">=5.0.0,<6" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "flake8-quotes" +version = "3.4.0" +description = "Flake8 lint for quotes." +optional = false +python-versions = "*" +files = [ + {file = "flake8-quotes-3.4.0.tar.gz", hash = "sha256:aad8492fb710a2d3eabe68c5f86a1428de650c8484127e14c43d0504ba30276c"}, +] + +[package.dependencies] +flake8 = "*" +setuptools = "*" + +[[package]] +name = "flake8-rst-docstrings" +version = "0.3.0" +description = "Python docstring reStructuredText (RST) validator for flake8" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flake8-rst-docstrings-0.3.0.tar.gz", hash = "sha256:d1ce22b4bd37b73cd86b8d980e946ef198cfcc18ed82fedb674ceaa2f8d1afa4"}, + {file = "flake8_rst_docstrings-0.3.0-py3-none-any.whl", hash = "sha256:f8c3c6892ff402292651c31983a38da082480ad3ba253743de52989bdc84ca1c"}, +] + +[package.dependencies] +flake8 = ">=3" +pygments = "*" +restructuredtext-lint = "*" + +[package.extras] +develop = ["build", "twine"] + +[[package]] +name = "flake8-string-format" +version = "0.3.0" +description = "string format checker, plugin for flake8" +optional = false +python-versions = "*" +files = [ + {file = "flake8-string-format-0.3.0.tar.gz", hash = "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2"}, + {file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"}, +] + +[package.dependencies] +flake8 = "*" + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mypy" version = "1.11.1" @@ -57,6 +406,218 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "pbr" +version = "6.0.0" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +files = [ + {file = "pbr-6.0.0-py2.py3-none-any.whl", hash = "sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda"}, + {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, +] + +[[package]] +name = "pep8-naming" +version = "0.14.1" +description = "Check PEP-8 naming conventions, plugin for flake8" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pep8-naming-0.14.1.tar.gz", hash = "sha256:1ef228ae80875557eb6c1549deafed4dabbf3261cfcafa12f773fe0db9be8a36"}, + {file = "pep8_naming-0.14.1-py3-none-any.whl", hash = "sha256:63f514fc777d715f935faf185dedd679ab99526a7f2f503abb61587877f7b1c5"}, +] + +[package.dependencies] +flake8 = ">=5.0.0" + +[[package]] +name = "pycodestyle" +version = "2.12.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, +] + +[[package]] +name = "pydocstyle" +version = "6.3.0" +description = "Python docstring style checker" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, + {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, +] + +[package.dependencies] +snowballstemmer = ">=2.2.0" + +[package.extras] +toml = ["tomli (>=1.2.3)"] + +[[package]] +name = "pyflakes" +version = "3.2.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "restructuredtext-lint" +version = "1.4.0" +description = "reStructuredText linter" +optional = false +python-versions = "*" +files = [ + {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, +] + +[package.dependencies] +docutils = ">=0.11,<1.0" + +[[package]] +name = "rich" +version = "13.7.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "setuptools" +version = "72.2.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-72.2.0-py3-none-any.whl", hash = "sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4"}, + {file = "setuptools-72.2.0.tar.gz", hash = "sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9"}, +] + +[package.extras] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "stevedore" +version = "5.2.0" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "stevedore-5.2.0-py3-none-any.whl", hash = "sha256:1c15d95766ca0569cad14cb6272d4d31dae66b011a929d7c18219c176ea1b5c9"}, + {file = "stevedore-5.2.0.tar.gz", hash = "sha256:46b93ca40e1114cea93d738a6c1e365396981bb6bb78c27045b7587c9473544d"}, +] + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + [[package]] name = "typing-extensions" version = "4.12.2" @@ -68,7 +629,42 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "wemake-python-styleguide" +version = "0.19.2" +description = "The strictest and most opinionated python linter ever" +optional = false +python-versions = "^3.9" +files = [] +develop = false + +[package.dependencies] +attrs = "*" +darglint = "^1.2" +flake8 = "^7.1" +flake8-bandit = "^4.1" +flake8-broken-line = "^1.0" +flake8-bugbear = "^24.2" +flake8-commas = "^4.0" +flake8-comprehensions = "^3.1" +flake8-debugger = "^4.0" +flake8-docstrings = "^1.3" +flake8-eradicate = "^1.5" +flake8-isort = "^6.0" +flake8-quotes = "^3.0" +flake8-rst-docstrings = "^0.3" +flake8-string-format = "^0.3" +pep8-naming = "^0.14" +pygments = "^2.4" +typing_extensions = ">=4.0,<5.0" + +[package.source] +type = "git" +url = "https://github.com/wemake-services/wemake-python-styleguide" +reference = "master" +resolved_reference = "1c4918c004fd01f2450a88671487f6c5d644a1c9" + [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "a52118f4153aa477ce8ee02aeabdb8935aa7c07561e541d26de45835b608874b" +content-hash = "a10c4ee7590f594dd684d771172d81df75f16bf29ea8922a8e97833e5fdb9fe0" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..9c77dd8 --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[installer] +no-binary = ["mypy"] diff --git a/pyproject.toml b/pyproject.toml index 3eaf111..2c05b64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,13 +6,17 @@ authors = ["Mihail Butvin "] readme = "README.md" [tool.poetry.scripts] -dora = "dora.main:main" +dora = "dora.__main__:main" [tool.poetry.dependencies] -# We use mypy internal functionality, not the public API, so changes in both mypy or python can lead to breaking our code +# We use mypy internal functionality, not the public API, so changes in both mypy or python can lead to breaking of our code python = "~3.12" mypy = "^1.11.1" +[tool.poetry.group.lint.dependencies] +isort = "^5.13.2" +# We use a trunk version of WPS to bump flake8-commas version. Waiting for the next release. +wemake-python-styleguide = { git = "https://github.com/wemake-services/wemake-python-styleguide", branch = "master" } [build-system] requires = ["poetry-core"] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..5799553 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,41 @@ +[isort] +profile = wemake + +[flake8] +format = wemake +max-line-length = 666 +min-name-length = 1 +inline-quotes = single +extend-ignore = WPS305 +per-file-ignores = + dora/__main__.py: WPS421 +exclude = + .venv, + hooks, + *.egg, + .eggs, + dist, + build, + docs, + .tox, + .git, + __pycache__, + .mypy_cache, + +show_violation_links = true + +[mypy] +strict = true +exclude = + .venv, + hooks, + ^.*\\.egg$, + .eggs, + dist, + build, + docs, + .tox, + .git, + __pycache__, + \\.mypy_cache, + diff --git a/tests/codebase/main.py b/tests/codebase/main.py index 689d5e7..cde37a4 100644 --- a/tests/codebase/main.py +++ b/tests/codebase/main.py @@ -1,7 +1,10 @@ # comment # no-args stub function -def no_args_function(): +from typing import TypeVar + + +def no_args_function(): # type: ignore pass @@ -49,13 +52,22 @@ def simple_args_only_kwargs_only_function(a: int, /, *, b: int) -> int: return a + b +T = TypeVar('T') + # generic function -def generic_function[T](a: T) -> T: + + +def generic_function(a: T) -> T: return a +K = TypeVar('K') +V = TypeVar('V') + # very complex function definition -def very_complex_function_definition[K, V]( + + +def very_complex_function_definition( a: K, b: V, c: dict[K, V] = {}, diff --git a/tests/codebase/subfolder/test2.py b/tests/codebase/subfolder/test2.py new file mode 100644 index 0000000..856f8c1 --- /dev/null +++ b/tests/codebase/subfolder/test2.py @@ -0,0 +1,9 @@ +def foo(a: int, b: int) -> str: + return str(a) + str(b) + + +a = 10 +b = 11 +print(a + b) + +print(foo(a, b)) diff --git a/tests/records/Given -h flag, help should be shown b/tests/records/Given -h flag, help should be shown new file mode 100644 index 0000000..cbfdce8 --- /dev/null +++ b/tests/records/Given -h flag, help should be shown @@ -0,0 +1,19 @@ +title: Given -h flag, help should be shown +args: ['dora', '-h'] +exitcode: 0 +stdout: +usage: dora [-h] [-t ] paths [paths ...] + +Search source files by type expressions. + +positional arguments: + paths The source files to search in. + +options: + -h, --help show this help message and exit + -t , --type-expression + The type expression to search for. If not provided, + all types in the file will be listed. + +stderr: + diff --git a/tests/records/Given directory, all files should be analyzed recursively b/tests/records/Given directory, all files should be analyzed recursively new file mode 100644 index 0000000..792f8e1 --- /dev/null +++ b/tests/records/Given directory, all files should be analyzed recursively @@ -0,0 +1,409 @@ +title: Given directory, all files should be analyzed recursively +args: ['dora', PosixPath('tests/codebase')] +exitcode: 0 +stdout: +tests/codebase/subfolder/test2.py:2:11 + builtins.str (OpExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:11 + builtins.str (CallExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:11 + Overload(def (object: builtins.object =) -> builtins.str, def (object: _collections_abc.Buffer, encoding: builtins.str =, errors: builtins.str =) -> builtins.str) (NameExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:15 + builtins.int (NameExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:20 + builtins.str (CallExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:20 + Overload(def (object: builtins.object =) -> builtins.str, def (object: _collections_abc.Buffer, encoding: builtins.str =, errors: builtins.str =) -> builtins.str) (NameExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:24 + builtins.int (NameExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:5:4 + Literal[10]? (IntExpr) + v +a = 10 + + +tests/codebase/subfolder/test2.py:5:0 +builtins.int (NameExpr) +v +a = 10 + + +tests/codebase/subfolder/test2.py:6:4 + Literal[11]? (IntExpr) + v +b = 11 + + +tests/codebase/subfolder/test2.py:6:0 +builtins.int (NameExpr) +v +b = 11 + + +tests/codebase/subfolder/test2.py:7:0 +None (CallExpr) +v +print(a + b) + + +tests/codebase/subfolder/test2.py:7:0 +Overload(def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[_typeshed.SupportsWrite[builtins.str], None] =, flush: Literal[False] =), def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[builtins._SupportsWriteAndFlush[builtins.str], None] =, flush: builtins.bool)) (NameExpr) +v +print(a + b) + + +tests/codebase/subfolder/test2.py:7:6 + builtins.int (OpExpr) + v +print(a + b) + + +tests/codebase/subfolder/test2.py:7:6 + builtins.int (NameExpr) + v +print(a + b) + + +tests/codebase/subfolder/test2.py:7:10 + builtins.int (NameExpr) + v +print(a + b) + + +tests/codebase/subfolder/test2.py:9:0 +None (CallExpr) +v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:0 +Overload(def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[_typeshed.SupportsWrite[builtins.str], None] =, flush: Literal[False] =), def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[builtins._SupportsWriteAndFlush[builtins.str], None] =, flush: builtins.bool)) (NameExpr) +v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:6 + builtins.str (CallExpr) + v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:6 + def (a: builtins.int, b: builtins.int) -> builtins.str (NameExpr) + v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:10 + builtins.int (NameExpr) + v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:13 + builtins.int (NameExpr) + v +print(foo(a, b)) + + +tests/codebase/main.py:13:11 + None (NameExpr) + v + return None + + +tests/codebase/main.py:18:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:18:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:18:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:23:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:23:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:23:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:28:11 + builtins.int (OpExpr) + v + return a + b + c + + +tests/codebase/main.py:28:11 + builtins.int (OpExpr) + v + return a + b + c + + +tests/codebase/main.py:28:11 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:28:15 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:28:19 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:35:13 + Literal[1]? (IntExpr) + v + c: int = 1, + + +tests/codebase/main.py:37:11 + builtins.int (OpExpr) + v + return a + b + c + + +tests/codebase/main.py:37:11 + builtins.int (OpExpr) + v + return a + b + c + + +tests/codebase/main.py:37:11 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:37:15 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:37:19 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:42:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:42:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:42:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:47:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:47:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:47:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:52:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:52:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:52:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:55:4 + Any (CallExpr) + v +T = TypeVar('T') + + +tests/codebase/main.py:55:-1 +Any (TypeVarExpr) +v +T = TypeVar('T') + + +tests/codebase/main.py:55:0 +Any (NameExpr) +v +T = TypeVar('T') + + +tests/codebase/main.py:61:11 + T`-1 (NameExpr) + v + return a + + +tests/codebase/main.py:64:4 + Any (CallExpr) + v +K = TypeVar('K') + + +tests/codebase/main.py:64:-1 +Any (TypeVarExpr) +v +K = TypeVar('K') + + +tests/codebase/main.py:64:0 +Any (NameExpr) +v +K = TypeVar('K') + + +tests/codebase/main.py:65:4 + Any (CallExpr) + v +V = TypeVar('V') + + +tests/codebase/main.py:65:-1 +Any (TypeVarExpr) +v +V = TypeVar('V') + + +tests/codebase/main.py:65:0 +Any (NameExpr) +v +V = TypeVar('V') + + +tests/codebase/main.py:73:20 + builtins.dict[K`-1, V`-2] (DictExpr) + v + c: dict[K, V] = {}, + + +tests/codebase/main.py:77:13 + Literal[1]? (IntExpr) + v + e: int = 1, + + +tests/codebase/main.py:79:11 + None (NameExpr) + v + return None + + +tests/codebase/test.py:1:4 + Literal['Hello, world!']? (StrExpr) + v +x = 'Hello, world!' + + +tests/codebase/test.py:1:0 +builtins.str (NameExpr) +v +x = 'Hello, world!' + + +tests/codebase/test.py:2:0 +None (CallExpr) +v +print(x) + + +tests/codebase/test.py:2:0 +Overload(def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[_typeshed.SupportsWrite[builtins.str], None] =, flush: Literal[False] =), def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[builtins._SupportsWriteAndFlush[builtins.str], None] =, flush: builtins.bool)) (NameExpr) +v +print(x) + + +tests/codebase/test.py:2:6 + builtins.str (NameExpr) + v +print(x) + + + +stderr: + diff --git a/tests/records/Given duplicated pathes, mypy error expected b/tests/records/Given duplicated pathes, mypy error expected new file mode 100644 index 0000000..d12b2d3 --- /dev/null +++ b/tests/records/Given duplicated pathes, mypy error expected @@ -0,0 +1,10 @@ +title: Given duplicated pathes, mypy error expected +args: ['dora', PosixPath('tests/codebase'), PosixPath('tests/codebase/main.py')] +exitcode: 1 +stdout: + +stderr: +tests/codebase/main.py: error: Duplicate module named "tests.codebase.main" (also at "tests/codebase/main.py") +tests/codebase/main.py: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules for more info +tests/codebase/main.py: note: Common resolutions include: a) using `--exclude` to avoid checking one of them, b) adding `__init__.py` somewhere, c) using `--explicit-package-bases` or adjusting MYPYPATH + diff --git a/tests/records/Given no args, usage should be shown b/tests/records/Given no args, usage should be shown new file mode 100644 index 0000000..24cc1d8 --- /dev/null +++ b/tests/records/Given no args, usage should be shown @@ -0,0 +1,9 @@ +title: Given no args, usage should be shown +args: ['dora'] +exitcode: 2 +stdout: + +stderr: +usage: dora [-h] [-t ] paths [paths ...] +dora: error: the following arguments are required: paths + diff --git a/tests/records/Given non-existing file, error should be shown b/tests/records/Given non-existing file, error should be shown new file mode 100644 index 0000000..3c0d004 --- /dev/null +++ b/tests/records/Given non-existing file, error should be shown @@ -0,0 +1,9 @@ +title: Given non-existing file, error should be shown +args: ['dora', 'non-existing-file.py'] +exitcode: 2 +stdout: + +stderr: +usage: dora [-h] [-t ] paths [paths ...] +dora: error: The path "non-existing-file.py" does not exist. + diff --git a/tests/records/Search for `builtins.str` b/tests/records/Search for `builtins.str` new file mode 100644 index 0000000..6e232de --- /dev/null +++ b/tests/records/Search for `builtins.str` @@ -0,0 +1,43 @@ +title: Search for `builtins.str` +args: ['dora', PosixPath('tests/codebase'), '-t', 'builtins.str'] +exitcode: 0 +stdout: +tests/codebase/subfolder/test2.py:2:11 + builtins.str (OpExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:11 + builtins.str (CallExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:20 + builtins.str (CallExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:9:6 + builtins.str (CallExpr) + v +print(foo(a, b)) + + +tests/codebase/test.py:1:0 +builtins.str (NameExpr) +v +x = 'Hello, world!' + + +tests/codebase/test.py:2:6 + builtins.str (NameExpr) + v +print(x) + + + +stderr: + diff --git a/tests/records/Search for `def (a: builtins.int, b: builtins.int) -> builtins.str` b/tests/records/Search for `def (a: builtins.int, b: builtins.int) -> builtins.str` new file mode 100644 index 0000000..97660a8 --- /dev/null +++ b/tests/records/Search for `def (a: builtins.int, b: builtins.int) -> builtins.str` @@ -0,0 +1,13 @@ +title: Search for `def (a: builtins.int, b: builtins.int) -> builtins.str` +args: ['dora', PosixPath('tests/codebase'), '-t', 'def (a: builtins.int, b: builtins.int) -> builtins.str'] +exitcode: 0 +stdout: +tests/codebase/subfolder/test2.py:9:6 + def (a: builtins.int, b: builtins.int) -> builtins.str (NameExpr) + v +print(foo(a, b)) + + + +stderr: + diff --git a/tests/records/Without specified type expression, all types should be displayed b/tests/records/Without specified type expression, all types should be displayed new file mode 100644 index 0000000..f3eed2c --- /dev/null +++ b/tests/records/Without specified type expression, all types should be displayed @@ -0,0 +1,409 @@ +title: Without specified type expression, all types should be displayed +args: ['dora', PosixPath('tests/codebase')] +exitcode: 0 +stdout: +tests/codebase/subfolder/test2.py:2:11 + builtins.str (OpExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:11 + builtins.str (CallExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:11 + Overload(def (object: builtins.object =) -> builtins.str, def (object: _collections_abc.Buffer, encoding: builtins.str =, errors: builtins.str =) -> builtins.str) (NameExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:15 + builtins.int (NameExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:20 + builtins.str (CallExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:20 + Overload(def (object: builtins.object =) -> builtins.str, def (object: _collections_abc.Buffer, encoding: builtins.str =, errors: builtins.str =) -> builtins.str) (NameExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:2:24 + builtins.int (NameExpr) + v + return str(a) + str(b) + + +tests/codebase/subfolder/test2.py:5:4 + Literal[10]? (IntExpr) + v +a = 10 + + +tests/codebase/subfolder/test2.py:5:0 +builtins.int (NameExpr) +v +a = 10 + + +tests/codebase/subfolder/test2.py:6:4 + Literal[11]? (IntExpr) + v +b = 11 + + +tests/codebase/subfolder/test2.py:6:0 +builtins.int (NameExpr) +v +b = 11 + + +tests/codebase/subfolder/test2.py:7:0 +None (CallExpr) +v +print(a + b) + + +tests/codebase/subfolder/test2.py:7:0 +Overload(def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[_typeshed.SupportsWrite[builtins.str], None] =, flush: Literal[False] =), def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[builtins._SupportsWriteAndFlush[builtins.str], None] =, flush: builtins.bool)) (NameExpr) +v +print(a + b) + + +tests/codebase/subfolder/test2.py:7:6 + builtins.int (OpExpr) + v +print(a + b) + + +tests/codebase/subfolder/test2.py:7:6 + builtins.int (NameExpr) + v +print(a + b) + + +tests/codebase/subfolder/test2.py:7:10 + builtins.int (NameExpr) + v +print(a + b) + + +tests/codebase/subfolder/test2.py:9:0 +None (CallExpr) +v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:0 +Overload(def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[_typeshed.SupportsWrite[builtins.str], None] =, flush: Literal[False] =), def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[builtins._SupportsWriteAndFlush[builtins.str], None] =, flush: builtins.bool)) (NameExpr) +v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:6 + builtins.str (CallExpr) + v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:6 + def (a: builtins.int, b: builtins.int) -> builtins.str (NameExpr) + v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:10 + builtins.int (NameExpr) + v +print(foo(a, b)) + + +tests/codebase/subfolder/test2.py:9:13 + builtins.int (NameExpr) + v +print(foo(a, b)) + + +tests/codebase/main.py:13:11 + None (NameExpr) + v + return None + + +tests/codebase/main.py:18:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:18:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:18:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:23:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:23:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:23:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:28:11 + builtins.int (OpExpr) + v + return a + b + c + + +tests/codebase/main.py:28:11 + builtins.int (OpExpr) + v + return a + b + c + + +tests/codebase/main.py:28:11 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:28:15 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:28:19 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:35:13 + Literal[1]? (IntExpr) + v + c: int = 1, + + +tests/codebase/main.py:37:11 + builtins.int (OpExpr) + v + return a + b + c + + +tests/codebase/main.py:37:11 + builtins.int (OpExpr) + v + return a + b + c + + +tests/codebase/main.py:37:11 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:37:15 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:37:19 + builtins.int (NameExpr) + v + return a + b + c + + +tests/codebase/main.py:42:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:42:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:42:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:47:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:47:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:47:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:52:11 + builtins.int (OpExpr) + v + return a + b + + +tests/codebase/main.py:52:11 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:52:15 + builtins.int (NameExpr) + v + return a + b + + +tests/codebase/main.py:55:4 + Any (CallExpr) + v +T = TypeVar('T') + + +tests/codebase/main.py:55:-1 +Any (TypeVarExpr) +v +T = TypeVar('T') + + +tests/codebase/main.py:55:0 +Any (NameExpr) +v +T = TypeVar('T') + + +tests/codebase/main.py:61:11 + T`-1 (NameExpr) + v + return a + + +tests/codebase/main.py:64:4 + Any (CallExpr) + v +K = TypeVar('K') + + +tests/codebase/main.py:64:-1 +Any (TypeVarExpr) +v +K = TypeVar('K') + + +tests/codebase/main.py:64:0 +Any (NameExpr) +v +K = TypeVar('K') + + +tests/codebase/main.py:65:4 + Any (CallExpr) + v +V = TypeVar('V') + + +tests/codebase/main.py:65:-1 +Any (TypeVarExpr) +v +V = TypeVar('V') + + +tests/codebase/main.py:65:0 +Any (NameExpr) +v +V = TypeVar('V') + + +tests/codebase/main.py:73:20 + builtins.dict[K`-1, V`-2] (DictExpr) + v + c: dict[K, V] = {}, + + +tests/codebase/main.py:77:13 + Literal[1]? (IntExpr) + v + e: int = 1, + + +tests/codebase/main.py:79:11 + None (NameExpr) + v + return None + + +tests/codebase/test.py:1:4 + Literal['Hello, world!']? (StrExpr) + v +x = 'Hello, world!' + + +tests/codebase/test.py:1:0 +builtins.str (NameExpr) +v +x = 'Hello, world!' + + +tests/codebase/test.py:2:0 +None (CallExpr) +v +print(x) + + +tests/codebase/test.py:2:0 +Overload(def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[_typeshed.SupportsWrite[builtins.str], None] =, flush: Literal[False] =), def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[builtins._SupportsWriteAndFlush[builtins.str], None] =, flush: builtins.bool)) (NameExpr) +v +print(x) + + +tests/codebase/test.py:2:6 + builtins.str (NameExpr) + v +print(x) + + + +stderr: + diff --git a/tests/results/Given extra args, help should be shown b/tests/results/Given extra args, help should be shown deleted file mode 100644 index 7454f49..0000000 --- a/tests/results/Given extra args, help should be shown +++ /dev/null @@ -1,12 +0,0 @@ -Given extra args, help should be shown -returncode: 1 -stdout: -Usage: dora [] - -Args: - file: The Python file to analyze - type_expression: The type expression to search for. If not provided, all types - in the file will be listed. - -stderr: - diff --git a/tests/results/Given no args, help should be shown b/tests/results/Given no args, help should be shown deleted file mode 100644 index 56a89f5..0000000 --- a/tests/results/Given no args, help should be shown +++ /dev/null @@ -1,12 +0,0 @@ -Given no args, help should be shown -returncode: 1 -stdout: -Usage: dora [] - -Args: - file: The Python file to analyze - type_expression: The type expression to search for. If not provided, all types - in the file will be listed. - -stderr: - diff --git a/tests/results/Given non-existing file, error should be shown b/tests/results/Given non-existing file, error should be shown deleted file mode 100644 index 1dbd65e..0000000 --- a/tests/results/Given non-existing file, error should be shown +++ /dev/null @@ -1,7 +0,0 @@ -Given non-existing file, error should be shown -returncode: 1 -stdout: -File non-existing-file.py does not exist - -stderr: - diff --git a/tests/results/Given path only, all types should be displayed b/tests/results/Given path only, all types should be displayed deleted file mode 100644 index 1249da3..0000000 --- a/tests/results/Given path only, all types should be displayed +++ /dev/null @@ -1,114 +0,0 @@ -Given path only, all types should be displayed -returncode: 0 -stdout: -tests/codebase/main.py:10:11 - Expr: return None - Type: None -tests/codebase/main.py:15:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:15:15 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:15:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:20:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:20:15 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:20:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:25:11 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:25:15 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:25:11 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:25:19 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:25:11 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:32:13 - Expr: c: int = 1, - Type: Literal[1]? -tests/codebase/main.py:34:11 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:34:15 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:34:11 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:34:19 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:34:11 - Expr: return a + b + c - Type: builtins.int -tests/codebase/main.py:39:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:39:15 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:39:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:44:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:44:15 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:44:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:49:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:49:15 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:49:11 - Expr: return a + b - Type: builtins.int -tests/codebase/main.py:54:11 - Expr: return a - Type: Any -tests/codebase/main.py:61:20 - Expr: c: dict[K, V] = {}, - Type: builtins.dict[Any, Any] -tests/codebase/main.py:65:13 - Expr: e: int = 1, - Type: Literal[1]? -tests/codebase/main.py:67:11 - Expr: return None - Type: None -tests/codebase/test.py:1:0 - Expr: x = 'Hello, world!' - Type: builtins.str -tests/codebase/test.py:1:4 - Expr: x = 'Hello, world!' - Type: Literal['Hello, world!']? -tests/codebase/test.py:2:0 - Expr: print(x) - Type: Overload(def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[_typeshed.SupportsWrite[builtins.str], None] =, flush: Literal[False] =), def (*values: builtins.object, sep: Union[builtins.str, None] =, end: Union[builtins.str, None] =, file: Union[builtins._SupportsWriteAndFlush[builtins.str], None] =, flush: builtins.bool)) -tests/codebase/test.py:2:6 - Expr: print(x) - Type: builtins.str -tests/codebase/test.py:2:0 - Expr: print(x) - Type: None - -stderr: - diff --git a/tests/results/Search for builtins.str b/tests/results/Search for builtins.str deleted file mode 100644 index af53063..0000000 --- a/tests/results/Search for builtins.str +++ /dev/null @@ -1,8 +0,0 @@ -Search for builtins.str -returncode: 0 -stdout: -tests/codebase/test.py:1:0 x = 'Hello, world!' -tests/codebase/test.py:2:6 print(x) - -stderr: - diff --git a/tests/test.py b/tests/test.py index fab1c18..2e76040 100644 --- a/tests/test.py +++ b/tests/test.py @@ -3,39 +3,42 @@ These tests run a dora binary and compare output with previously recorded. """ import os +import shutil import subprocess from pathlib import Path +from typing import TypeAlias CODEBASE_PATH = Path(__file__).parent.joinpath('codebase').relative_to(Path.cwd()) -RESULTS = Path(__file__).parent.joinpath('results').relative_to(Path.cwd()) -RESULTS.mkdir(exist_ok=True) +RECORDS = Path(__file__).parent.joinpath('records').relative_to(Path.cwd()) +RECORDS.mkdir(exist_ok=True) def _run(args: list[str]) -> subprocess.CompletedProcess: return subprocess.run(args, capture_output=True, text=True) -def _build_result_text(title: str, result: subprocess.CompletedProcess) -> str: - result_text = f'{title}\n' - result_text += f'returncode: {result.returncode}\n' +def _build_result_text(title: str, args: list[str], result: subprocess.CompletedProcess) -> str: + result_text = f'title: {title}\n' + result_text += f'args: {args}\n' + result_text += f'exitcode: {result.returncode}\n' result_text += f'stdout:\n{result.stdout}\n' result_text += f'stderr:\n{result.stderr}\n' return result_text def record_test_case(title: str, args: list[str]) -> None: - golden_result_path = RESULTS.joinpath(title) + golden_result_path = RECORDS.joinpath(title) result = _run(args) - golden_result_text = _build_result_text(title, result) + golden_result_text = _build_result_text(title, args, result) golden_result_path.write_text(golden_result_text) def rerun_test_case(title: str, args: list[str]) -> str | None: - golden_result_path = RESULTS.joinpath(title) + golden_result_path = RECORDS.joinpath(title) golden_result_text = golden_result_path.read_text() result = _run(args) - result_text = _build_result_text(title, result) + result_text = _build_result_text(title, args, result) if result_text != golden_result_text: failed_result_path = golden_result_path.with_suffix('.failed') failed_result_path.write_text(result_text) @@ -46,23 +49,30 @@ def rerun_test_case(title: str, args: list[str]) -> str | None: test_cases = [ - ('Given no args, help should be shown', ['dora']), - ('Given extra args, help should be shown', ['dora', 'a', 'b', 'c']), + ('Given no args, usage should be shown', ['dora']), + ('Given -h flag, help should be shown', ['dora', '-h']), ('Given non-existing file, error should be shown', ['dora', 'non-existing-file.py']), - ('Given path only, all types should be displayed', ['dora', CODEBASE_PATH]), - ('Search for builtins.str', ['dora', CODEBASE_PATH, 'builtins.str']), + ('Without specified type expression, all types should be displayed', ['dora', CODEBASE_PATH]), + ('Given directory, all files should be analyzed recursively', ['dora', CODEBASE_PATH]), + ('Given duplicated pathes, mypy error expected', ['dora', CODEBASE_PATH, CODEBASE_PATH / 'main.py']), + ('Search for `builtins.str`', ['dora', CODEBASE_PATH, '-t', 'builtins.str']), + ('Search for `def (a: builtins.int, b: builtins.int) -> builtins.str`', ['dora', CODEBASE_PATH, '-t', 'def (a: builtins.int, b: builtins.int) -> builtins.str']), ] +TestCase: TypeAlias = tuple[str, list[str]] -def record_test_cases() -> None: - print('RECORD') +def clear_cache() -> None: + for path in Path.cwd().rglob('.mypy_cache'): + shutil.rmtree(path) + + +def record_test_cases(test_cases: list[TestCase]) -> None: for title, args in test_cases: print(title, args) record_test_case(title, args) -def rerun_test_cases() -> bool: - print('RERUN') +def rerun_test_cases(test_cases: list[TestCase]) -> bool: failed = False for title, args in test_cases: print(title, args, end=' ') @@ -78,8 +88,25 @@ def rerun_test_cases() -> bool: if __name__ == '__main__': + # check if results contain redundant test cases + recorded_test_cases = [result.name for result in RECORDS.glob('*')] + redundant_test_cases = set(recorded_test_cases) - {title for title, _ in test_cases} + if redundant_test_cases: + print(f'Results directory contains redundant test cases: {redundant_test_cases}') + exit(2) + if os.environ.get('DORA_SNAPSHOT_RECORD'): - record_test_cases() + print('Recording test cases') + clear_cache() + record_test_cases(test_cases) else: - if rerun_test_cases(): + print('Rerunning test cases w/o cache') + clear_cache() + + if rerun_test_cases(test_cases): + exit(1) + + print() + print('Rerunning test cases with cache') + if rerun_test_cases(test_cases): exit(1)