From 918451513d854d6498dd6c6bd552bb77b18f3b8a Mon Sep 17 00:00:00 2001 From: "Edward K. Ream" Date: Tue, 25 Jun 2024 13:10:31 -0500 Subject: [PATCH 1/4] Replace 'counter' function with 'collection.Counter'. All tests pass --- pyflakes/checker.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 47a59ba2..4817c2b6 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -164,18 +164,6 @@ def __missing__(self, node_class): self[node_class] = fields = self._get_fields(node_class) return fields - -def counter(items): - """ - Simplest required implementation of collections.Counter. Required as 2.6 - does not have Counter in collections. - """ - results = {} - for item in items: - results[item] = results.get(item, 0) + 1 - return results - - def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): """ Yield all direct child nodes of *node*, that is, all fields that @@ -1773,6 +1761,7 @@ def DICT(self, node): # Complain if there are duplicate keys with different values # If they have the same value it's not going to cause potentially # unexpected behaviour so we'll not complain. + counter = collections.Counter keys = [ convert_to_value(key) for key in node.keys ] From 2d8f8de7f7b9799e4f6ba57a93d1b063277fbbb9 Mon Sep 17 00:00:00 2001 From: "Edward K. Ream" Date: Tue, 25 Jun 2024 20:23:00 -0500 Subject: [PATCH 2/4] Replace 'counter' by 'collections.Counter' --- pyflakes/checker.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 4817c2b6..1036434e 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -1761,12 +1761,11 @@ def DICT(self, node): # Complain if there are duplicate keys with different values # If they have the same value it's not going to cause potentially # unexpected behaviour so we'll not complain. - counter = collections.Counter keys = [ convert_to_value(key) for key in node.keys ] - key_counts = counter(keys) + key_counts = collections.Counter(keys) duplicate_keys = [ key for key, count in key_counts.items() if count > 1 @@ -1775,7 +1774,7 @@ def DICT(self, node): for key in duplicate_keys: key_indices = [i for i, i_key in enumerate(keys) if i_key == key] - values = counter( + values = collections.Counter( convert_to_value(node.values[index]) for index in key_indices ) From fe91e7b1ee5c424200851709687d970c6f92f961 Mon Sep 17 00:00:00 2001 From: "Edward K. Ream" Date: Wed, 26 Jun 2024 04:49:52 -0500 Subject: [PATCH 3/4] Restore second blank line after _FieldsOrder class --- ekr_pyflakes.leo | 13701 ++++++++++++++++++++++++++++++++++++++++++ pyflakes/checker.py | 1 + 2 files changed, 13702 insertions(+) create mode 100644 ekr_pyflakes.leo diff --git a/ekr_pyflakes.leo b/ekr_pyflakes.leo new file mode 100644 index 00000000..825da851 --- /dev/null +++ b/ekr_pyflakes.leo @@ -0,0 +1,13701 @@ + + + + + + + + +Startup +@persistence +@settings + Buttons +@ignore Disabled buttons +@button clean-text +@button create decorators +create_d +create_decorator +create_decorators +create_fixups +find_class +find_next_clone +munge_lines +run + +@button introspect +@button join-path +@button make-decorators2 +create_d +create_decorator +create_decorators V2 +define_s +munge_lines +run V2 + +@button print-gnx +@button print-ss +@button print-ua +@button rclick-test +@rclick hi + +@button set-ua +@button split-path +@button timeit +@button toggle-debug-app +@button unit-tests +@command test @key=Ctrl-F7 + +@@button show-uas +@@button count-children + +@bool allow-text-zoom = True +@bool check-python-code-on-write = True +@bool use-german-keyboard = False +@bool use-mouse-expand-gestures = False +@data exec-script-commands +@data exec-script-patterns +@data history-list +@ignore Chapters +@chapter 1 +abc node 1 +child + +cloned node in chapter + +@chapter 2 +Chapter two +Second node + + + +@chapter 3 + +@rclick say-hi @args=say hi +Abbreviation settings +@bool enable-abbreviations = True +@outline-data tree-abbreviations +@organizer 1 +@organizer 2 +demo;; +@@button MyDemo @key=Ctrl-9 +<< imports >> +script_string +class myDemo +wrappers + + + +per-commander-plugin;; +@@file pluginname.py +<< docstring >> +<< version history >> +<< imports >> +init +onCreate +class pluginController +__init__ + + + + +importer;; +@@file importers/{|{x=name}|}.py +class {|{x=cap_name}|}_Importer +{|{x=name}|}.Overrides +{|{x=name}|}.clean_headline +{|{x=name}|}.clean_nodes + + +class class {|{x=cap_name}|}_ScanState +{|{x=name}|}_state.level +{|{x=name}|}_state.update + + + + + +Appearance settings +@bool log-pane-wraps = False +@bool recent-files-group-always = True +@bool show-iconbar = True +@bool show-tips = False +@bool stayInTreeAfterSelect = True +@bool use-chapter-tabs = True +@bool use-chapters = True +@bool use-gutter = False +@int qweb-view-font-size = 30 +@string initial-split-orientation = vertical + +Coloring settings +@bool color-doc-parts-as-rest = True +@bool use-pygments = False +@bool use-pygments-styles = False +@color head-bg = @mistyrose2 +@string pygments-style-name = leonine +@string target-language = python + +Command settings +@bool create-at-persistence-nodes-automatically = True +@bool enable-persistence = True +@bool make-node-conflicts-node = True +@bool run-pyflakes-on-write = True +@bool use-jedi = True +@bool use-qcompleter = False +@bool warn-about-redefined-shortcuts = True +@int auto-justify = 80 +rst3 path options +@string rst3-write-intermediate-extension = .txt +@string rst3-default-path = None +@string rst3-stylesheet-name = default.css +@string rst3-stylesheet-path = None +@string rst3-publish-argv-for-missing-stylesheets = None + + +Declutter +@bool tree-declutter = False +@data tree-declutter-patterns +--- unused patterns + About Decluttering +Rule & replacement lines +Style lines + +declutter: add icon to folders and remove... +declutter: demo pattern +declutter: hide org-mode tags +declutter: replace @<file> with an icon +declutter: show last part of long filenames + + +Environment settings +@ifenv COMPUTERNAME, edreamleo-pc, other-pc +@bool computername = True + +@ifenv xyzzy, abc +@bool xyzzy = True + +@ifplatform win32,linux2 +@string platform = not-mac + + +File settings +@bool open-with-clean-filenames = True +@bool check-for-changed-external-files = True +@bool open-with-save-on-update = False +@bool open-with-uses-derived-file-extensions = True + +Find settings +@bool auto-scroll-find-tab = False +@bool close-find-dialog-after-search = False +@bool find-ignore-duplicates = False +@bool minibuffer-find-mode = True +@bool use-find-dialog = False + +Importer settings +@data import-html-tags +@data import-xml-tags + +make-stub-files settings +@bool stub-overwrite = True +@bool stub-trace-matches = False +@bool stub-trace-patterns = False +@bool stub-trace-reduce = False +@bool stub-trace-visitors = False +@bool stub-update = False +@data stub-def-name-patterns +@data stub-general-patterns +@data stub-prefix-lines +@string stub-output-directory = ~/stubs + +Plugins +@@@enabled-plugins +mod_http settings +@bool http-active = True +@int port = 8080 +@string rst-http-attributename = 'rst_http_attribute' + +viewrendered settings +@bool view-rendered-auto-create = False +@bool view-rendered-auto-hide = False +@string view-rendered-default-kind = rst + +wikiview plugin +@data wikiview-link-patterns +@bool wikiview-active = True + + +Scintilla settings +@bool qt-use-scintilla = False + +Sphinx settings +@string sphinx-command-directory = +@string sphinx-default-command = make html +@string sphinx-input-directory = None +@string sphinx-output-directory = None + +Syntax coloring settings +@@color rest.keyword2 = red +@@color rest.keyword4 = blue +@@color rest.leokeyword = green +@color forth.keyword3 = black +@color python.name = @solarized-yellow +@font rest.comment1 + +Vim mode +@string vim-mode-normal-border = border: 3px solid #268bd2 +@string vim-mode-insert-border = border: 3px solid #dc322f +@string vim-mode-visual-border = border: 3px solid gray +@string vim-mode-unfocused-border = border: 3px dashed #268bd2 +@bool vim-mode = False + + + +Buttons +@button backup +@ignore Disabled buttons +@button print-gnx + + +script: remove copyright +script: Recursive import + +*** To do +Files +@clean __init__.py +@clean __main__.py +@clean api.py +function: check +function: checkPath +function: isPythonFile +function: iterSourceCode +function: checkRecursive +function: _exitOnSignal +function: _get_version +function: main + +@clean checker.py +<< checker.py: globals >> +checker.py: utils +function: getAlternatives +const: FOR_TYPES +function: _is_singleton +function: _is_tuple_constant +function: _is_constant +function: _is_const_non_singleton +function: _is_name_or_attr +const: *_RE & VALID_CONVERSIONS +function: _must_match +function: parse_percent_format +class _FieldsOrder(dict) +function: iter_child_nodes (Uses _FieldsOrder) +function: convert_to_value +function: is_notimplemented_name_node + +class Binding +class Definition(Binding) +Definition.redefines + +class Builtin(Definition) +Builtin.__init__ +Builtin.__repr__ + +class UnhandledKeyType +class VariableKey +VariableKey.__init__ +VariableKey.__eq__ +VariableKey.__hash__ + +checker.py: Importation class & subclasses +class Importation(Definition) +Importation.__init__ +Importation.redefines +Importation._has_alias +Importation.source_statement +Importation.__str__ + +class SubmoduleImportation(Importation) +SubmoduleImportation.__init__ +SubmoduleImportation.redefines +SubmoduleImportation.__str__ +SubmoduleImportation.source_statement + +class ImportationFrom(Importation) +ImportationFrom.__init__ +ImportationFrom.__str__ +ImportationFrom.source_statement + +class StarImportation(Importation) +StarImportation.__init__ +StarImportation.source_statement +StarImportation.__str__ + +class FutureImportation(ImportationFrom) +FutureImportation.__init__ + + +class Argument(Binding) +class Assignment(Binding) & NamedExprAssignment(Assignment) +class Annotation(Binding) +Annotation.redefines + +class FunctionDefinition(Definition) +class ClassDefinition(Definition) +class ExportBinding(Binding) +ExportBinding.__init__ + +checker.py: Scopes +class Scope(dict) +class ClassScope(Scope) +class FunctionScope(Scope) +FunctionScope.__init__ +FunctionScope.unused_assignments +FunctionScope.unused_annotations + +class TypeScope(Scope) +class GeneratorScope(Scope) +class ModuleScope(Scope) +class DoctestScope(ModuleScope) +class DetectClassScopedMagic +const: _MAGIC_GLOBALS + +function: getNodeName +checker.py: Typing & Annotations +const: TYPING_MODULES +function: _is_typing_helper +function: _is_typing +function: _is_any_typing_member +function: is_typing_overload +class AnnotationState +function: in_annotation +function: in_string_annotation + +class Checker +<< Checker: class data >> +Checker.__init__ +Checker: Deferred functions +Checker.deferFunction +Checker._run_deferred + +Checker._in_doctest +Checker: Properties +Checker.futuresAllowed +Checker.annotationsFutureEnabled +Checker.scope & in_scope + +Checker: Utils +Checker.checkDeadScopes +Checker.report +Checker: Tree utils +Checker.getParent +Checker.getCommonAncestor +Checker.descendantOf +Checker._getAncestor +Checker.getScopeNode +Checker.differentForks + +Checker.addBinding +Checker._unknown_handler +Checker.getNodeHandler +Checker: handleNodeLoad/Store/Delete +Checker.handleNodeLoad +Checker.handleNodeStore +Checker.handleNodeDelete + +Checker._enter_annotation +Checker._in_postponed_annotation +Checker.handleChildren +Checker: is* +Checker.isLiteralTupleUnpacking +Checker.isDocstring + +Checker.getDocstring +Checker: handle* +Checker.handleNode +Checker.handleDoctests +Checker.handleStringAnnotation +Checker.handle_annotation_always_deferred +Checker.handleAnnotation + +Checker.ignore + +Checker: visitors & helpers +Checker.SUBSCRIPT +Checker._handle_string_dot_format +Checker.CALL +Checker._handle_percent_format +Checker.BINOP +Checker.CONSTANT & related operators +Checker.RAISE +Checker: additional node types +Checker.JOINEDSTR +Checker.DICT +Checker.IF & IFEXPR +Checker.ASSERT +Checker.GLOBAL & NONLOCAL +Checker.GENERATOREXP & *COMP +Checker.NAME +Checker.CONTINUE & BREAK +Checker.RETURN +Checker.YIELD +Checker.FUNCTIONDEF +Checker.LAMBDA +Checker.ARGUMENTS +Checker.ARG +Checker.CLASSDEF +Checker.AUGASSIGN +Checker.TUPLE & LIST +Checker.IMPORT +Checker.IMPORTFROM +Checker.TRY & TRYSTAR +Checker.EXCEPTHANDLER +Checker.ANNASSIGN +Checker.COMPARE +Checker.MATCH* & _match_target +Checker._type_param_scope (@contextlib.contextmanager) +Checker.TYPEVAR, PARAMSPEC & TYPEVARTUPLE +Checker.TYPEALIAS + + + +@clean messages.py +class Message +class UnusedImport +class RedefinedWhileUnused +class ImportShadowedByLoopVar +ImportShadowedByLoopVar.__init__ + +class ImportStarNotPermitted +ImportStarNotPermitted.__init__ + +class ImportStarUsed +ImportStarUsed.__init__ + +class ImportStarUsage +ImportStarUsage.__init__ + +class UndefinedName +UndefinedName.__init__ + +class DoctestSyntaxError +DoctestSyntaxError.__init__ + +class UndefinedExport +UndefinedExport.__init__ + +class UndefinedLocal +UndefinedLocal.__init__ + +class DuplicateArgument +DuplicateArgument.__init__ + +class MultiValueRepeatedKeyLiteral +MultiValueRepeatedKeyLiteral.__init__ + +class MultiValueRepeatedKeyVariable +MultiValueRepeatedKeyVariable.__init__ + +class LateFutureImport +class FutureFeatureNotDefined +FutureFeatureNotDefined.__init__ + +class UnusedVariable +UnusedVariable.__init__ + +class UnusedAnnotation +UnusedAnnotation.__init__ + +class ReturnOutsideFunction +class YieldOutsideFunction +class ContinueOutsideLoop +class BreakOutsideLoop +class DefaultExceptNotLast +class TwoStarredExpressions +class TooManyExpressionsInStarredAssignment +class IfTuple +class AssertTuple +class ForwardAnnotationSyntaxError +ForwardAnnotationSyntaxError.__init__ + +class RaiseNotImplemented +class InvalidPrintSyntax +class IsLiteral +class FStringMissingPlaceholders +class StringDotFormatExtraPositionalArguments +StringDotFormatExtraPositionalArguments.__init__ + +class StringDotFormatExtraNamedArguments +StringDotFormatExtraNamedArguments.__init__ + +class StringDotFormatMissingArgument +StringDotFormatMissingArgument.__init__ + +class StringDotFormatMixingAutomatic +class StringDotFormatInvalidFormat +StringDotFormatInvalidFormat.__init__ + +class PercentFormatInvalidFormat +PercentFormatInvalidFormat.__init__ + +class PercentFormatMixedPositionalAndNamed +class PercentFormatUnsupportedFormatCharacter +PercentFormatUnsupportedFormatCharacter.__init__ + +class PercentFormatPositionalCountMismatch +PercentFormatPositionalCountMismatch.__init__ + +class PercentFormatExtraNamedArguments +PercentFormatExtraNamedArguments.__init__ + +class PercentFormatMissingArgument +PercentFormatMissingArgument.__init__ + +class PercentFormatExpectedMapping +class PercentFormatExpectedSequence +class PercentFormatStarRequiresSequence + +@clean reporter.py +class Reporter +Reporter.__init__ +Reporter.unexpectedError +Reporter.syntaxError +Reporter.flake + +function: _makeDefaultReporter + +@clean setup.py +function: get_version +function: get_long_description + +@edit .gitignore +pyflakes/test +@@@clean __init__.py +@clean harness.py +class TestCase +TestCase.flakes + + +@clean test_api.py +function: withStderrTo +class Node +Node.__init__ + +class SysStreamCapturing +SysStreamCapturing.__init__ +SysStreamCapturing.__enter__ +SysStreamCapturing.__exit__ + +class LoggingReporter +LoggingReporter.__init__ +LoggingReporter.flake +LoggingReporter.unexpectedError +LoggingReporter.syntaxError + +class TestIterSourceCode +TestIterSourceCode.setUp +TestIterSourceCode.tearDown +TestIterSourceCode.makeEmptyFile +TestIterSourceCode.test_emptyDirectory +TestIterSourceCode.test_singleFile +TestIterSourceCode.test_onlyPythonSource +TestIterSourceCode.test_recurses +TestIterSourceCode.test_shebang +TestIterSourceCode.test_multipleDirectories +TestIterSourceCode.test_explicitFiles + +class TestReporter +TestReporter.test_syntaxError +TestReporter.test_syntaxErrorNoOffset +TestReporter.test_syntaxErrorNoText +TestReporter.test_multiLineSyntaxError +TestReporter.test_unexpectedError +TestReporter.test_flake + +class CheckTests +CheckTests.makeTempFile +CheckTests.assertHasErrors +CheckTests.getErrors +CheckTests.test_legacyScript +CheckTests.test_missingTrailingNewline +CheckTests.test_checkPathNonExisting +CheckTests.test_multilineSyntaxError +CheckTests.test_eofSyntaxError +CheckTests.test_eofSyntaxErrorWithTab +--- +CheckTests.test_nonDefaultFollowsDefaultSyntaxError +CheckTests.test_nonKeywordAfterKeywordSyntaxError +CheckTests.test_invalidEscape +CheckTests.test_permissionDenied +CheckTests.test_pyflakesWarning +CheckTests.test_encodedFileUTF8 +CheckTests.test_CRLFLineEndings +CheckTests.test_misencodedFileUTF8 +CheckTests.test_misencodedFileUTF16 +CheckTests.test_checkRecursive +CheckTests.test_stdinReportsErrors + +class IntegrationTests +IntegrationTests.setUp +IntegrationTests.tearDown +IntegrationTests.getPyflakesBinary +IntegrationTests.runPyflakes +IntegrationTests.test_goodFile +IntegrationTests.test_fileWithFlakes +IntegrationTests.test_errors_io +IntegrationTests.test_errors_syntax +IntegrationTests.xxx_test_readFromStdin + +class TestMain +TestMain.runPyflakes + + +@clean test_builtin.py +class TestBuiltins +TestBuiltins.test_builtin_unbound_local + +function: test_global_shadowing_builtin + +@clean test_code_segment.py +class TestCodeSegments +TestCodeSegments.test_function_segment + +function: test_class_segment +function: test_scope_class +function: test_scope_function +function: test_scope_async_function + +@clean test_dict.py +class Test +Test.test_duplicate_keys +Test.test_duplicate_keys_bytes_vs_unicode_py3 +Test.test_duplicate_values_bytes_vs_unicode_py3 +Test.test_multiple_duplicate_keys +Test.test_duplicate_keys_in_function + +function: test_duplicate_keys_in_lambda +function: test_duplicate_keys_tuples +function: test_duplicate_keys_tuples_int_and_float +function: test_duplicate_keys_ints +function: test_duplicate_keys_bools +function: test_duplicate_keys_bools_false +function: test_duplicate_keys_none +function: test_duplicate_variable_keys +function: test_duplicate_variable_values +function: test_duplicate_variable_values_same_value +function: test_duplicate_key_float_and_int +function: test_no_duplicate_key_error_same_value +function: test_no_duplicate_key_errors +function: test_no_duplicate_keys_tuples_same_first_element +function: test_no_duplicate_key_errors_func_call +function: test_no_duplicate_key_errors_bool_or_none +function: test_no_duplicate_key_errors_ints +function: test_no_duplicate_key_errors_vars +function: test_no_duplicate_key_errors_tuples +function: test_no_duplicate_key_errors_instance_attributes + +@clean test_doctests.py +class _DoctestMixin +_DoctestMixin.doctestify + +function: flakes +class Test +Test.test_scope_class + +function: test_nested_doctest_ignored +function: test_global_module_scope_pollution +function: test_global_undefined +function: test_nested_class +function: test_ignore_nested_function +function: test_inaccessible_scope_class +function: test_importBeforeDoctest +function: test_importBeforeAndInDoctest +function: test_importInDoctestAndAfter +function: test_offsetInDoctests +function: test_offsetInLambdasInDoctests +function: test_offsetAfterDoctests +function: test_syntaxErrorInDoctest +function: test_indentationErrorInDoctest +function: test_offsetWithMultiLineArgs +function: test_doctestCanReferToFunction +function: test_doctestCanReferToClass +function: test_noOffsetSyntaxErrorInDoctest +function: test_singleUnderscoreInDoctest +function: test_globalUnderscoreInDoctest +class TestOther +class TestImports +class TestUndefinedNames + +@clean test_imports.py +class TestImportationObject +TestImportationObject.test_import_basic +TestImportationObject.test_import_as +TestImportationObject.test_import_submodule +TestImportationObject.test_import_submodule_as +TestImportationObject.test_import_submodule_as_source_name +TestImportationObject.test_importfrom_relative +TestImportationObject.test_importfrom_relative_parent +TestImportationObject.test_importfrom_relative_with_module +TestImportationObject.test_importfrom_relative_with_module_as +TestImportationObject.test_importfrom_member +TestImportationObject.test_importfrom_submodule_member +TestImportationObject.test_importfrom_member_as +TestImportationObject.test_importfrom_submodule_member_as +TestImportationObject.test_importfrom_star +TestImportationObject.test_importfrom_star_relative +TestImportationObject.test_importfrom_future +TestImportationObject.test_unusedImport_underscore + +class Test +Test.test_unusedImport +Test.test_unusedImport_relative +Test.test_aliasedImport +Test.test_aliasedImportShadowModule +Test.test_usedImport +Test.test_usedImport_relative +Test.test_redefinedWhileUnused +Test.test_redefinedIf + +function: test_redefinedIfElse +function: test_redefinedTry +function: test_redefinedTryExcept +function: test_redefinedTryNested +function: test_redefinedTryExceptMulti +function: test_redefinedTryElse +function: test_redefinedTryExceptElse +function: test_redefinedTryExceptFinally +function: test_redefinedTryExceptElseFinally +function: test_redefinedByFunction +function: test_redefinedInNestedFunction +function: test_redefinedInNestedFunctionTwice +function: test_redefinedButUsedLater +function: test_redefinedByClass +function: test_redefinedBySubclass +function: test_redefinedInClass +function: test_importInClass +function: test_usedInFunction +function: test_shadowedByParameter +function: test_newAssignment +function: test_usedInGetattr +function: test_usedInSlice +function: test_usedInIfBody +function: test_usedInIfConditional +function: test_usedInElifConditional +function: test_usedInElse +function: test_usedInCall +function: test_usedInClass +function: test_usedInClassBase +function: test_notUsedInNestedScope +function: test_usedInFor +function: test_usedInForElse +function: test_redefinedByFor +function: test_shadowedByFor +function: test_shadowedByForDeep +function: test_usedInReturn +function: test_usedInOperators +function: test_usedInAssert +function: test_usedInSubscript +function: test_usedInLogic +function: test_usedInList +function: test_usedInTuple +function: test_usedInTry +function: test_usedInExcept +function: test_redefinedByExcept +function: test_usedInRaise +function: test_usedInYield +function: test_usedInDict +function: test_usedInParameterDefault +function: test_usedInAttributeAssign +function: test_usedInKeywordArg +function: test_usedInAssignment +function: test_usedInListComp +function: test_usedInTryFinally +function: test_usedInWhile +function: test_usedInGlobal +function: test_usedAndGlobal +function: test_assignedToGlobal +function: test_usedInExec +function: test_usedInLambda +function: test_shadowedByLambda +function: test_usedInSliceObj +function: test_unusedInNestedScope +function: test_methodsDontUseClassScope +function: test_nestedFunctionsNestScope +function: test_nestedClassAndFunctionScope +function: test_importStar +function: test_importStar_relative +function: test_localImportStar +function: test_packageImport +function: test_unusedPackageImport +function: test_duplicateSubmoduleImport +function: test_differentSubmoduleImport +function: test_used_package_with_submodule_import +function: test_used_package_with_submodule_import_of_alias +function: test_unused_package_with_submodule_import +function: test_assignRHSFirst +function: test_tryingMultipleImports +function: test_nonGlobalDoesNotRedefine +function: test_functionsRunLater +function: test_functionNamesAreBoundNow +function: test_ignoreNonImportRedefinitions +function: test_importingForImportError +function: test_importedInClass +function: test_importUsedInMethodDefinition +function: test_futureImport +function: test_futureImportFirst +function: test_futureImportUsed +function: test_futureImportUndefined +function: test_futureImportStar +class TestSpecialAll +TestSpecialAll.test_ignoredInFunction + +function: test_ignoredInClass +function: test_ignored_when_not_directly_assigned +function: test_warningSuppressed +function: test_augmentedAssignment +function: test_list_concatenation_assignment +function: test_tuple_concatenation_assignment +function: test_all_with_attributes +function: test_all_with_names +function: test_all_with_attributes_added +function: test_all_mixed_attributes_and_strings +function: test_unboundExported +function: test_importStarExported +function: test_importStarNotExported +function: test_usedInGenExp +function: test_redefinedByGenExp +function: test_usedAsDecorator +function: test_usedAsClassDecorator + +@clean test_is_literal.py +class Test +Test.test_is_str + +function: test_is_bytes +function: test_is_unicode +function: test_is_int +function: test_is_true +function: test_is_false +function: test_is_not_str +function: test_is_not_bytes +function: test_is_not_unicode +function: test_is_not_int +function: test_is_not_true +function: test_is_not_false +function: test_left_is_str +function: test_left_is_bytes +function: test_left_is_unicode +function: test_left_is_int +function: test_left_is_true +function: test_left_is_false +function: test_left_is_not_str +function: test_left_is_not_bytes +function: test_left_is_not_unicode +function: test_left_is_not_int +function: test_left_is_not_true +function: test_left_is_not_false +function: test_chained_operators_is_true +function: test_chained_operators_is_str +function: test_chained_operators_is_true_end +function: test_chained_operators_is_str_end +function: test_is_tuple_constant +function: test_is_tuple_constant_containing_constants +function: test_is_tuple_containing_variables_ok + +@clean test_match.py +class TestMatch +TestMatch.test_match_bindings + +function: test_match_pattern_matched_class +function: test_match_placeholder +function: test_match_singleton +function: test_match_or_pattern +function: test_match_star +function: test_match_double_star +function: test_defined_in_different_branches + +@clean test_other.py +class Test +Test.test_duplicateArgs +Test.test_localReferencedBeforeAssignment + +function: test_redefinedInGenerator +function: test_redefinedInSetComprehension +function: test_redefinedInDictComprehension +function: test_redefinedFunction +function: test_redefined_function_shadows_variable +function: test_redefinedUnderscoreFunction +function: test_redefinedUnderscoreImportation +function: test_redefinedClassFunction +function: test_redefinedIfElseFunction +function: test_redefinedIfFunction +function: test_redefinedTryExceptFunction +function: test_redefinedTryFunction +function: test_redefinedIfElseInListComp +function: test_functionDecorator +function: test_classFunctionDecorator +function: test_modernProperty +function: test_unaryPlus +function: test_undefinedBaseClass +function: test_classNameUndefinedInClassBody +function: test_classNameDefinedPreviously +function: test_classRedefinition +function: test_functionRedefinedAsClass +function: test_classRedefinedAsFunction +function: test_classWithReturn +function: test_moduleWithReturn +function: test_classWithYield +function: test_moduleWithYield +function: test_classWithYieldFrom +function: test_moduleWithYieldFrom +function: test_continueOutsideLoop +function: test_continueInsideLoop +function: test_breakOutsideLoop +function: test_breakInsideLoop +function: test_defaultExceptLast +function: test_defaultExceptNotLast +function: test_starredAssignmentNoError +function: test_starredAssignmentErrors +function: test_doubleAssignment +function: test_doubleAssignmentConditionally +function: test_doubleAssignmentWithUse +function: test_comparison +function: test_identity +function: test_containment +function: test_loopControl +function: test_ellipsis +function: test_extendedSlice +function: test_varAugmentedAssignment +function: test_attrAugmentedAssignment +function: test_globalDeclaredInDifferentScope +function: test_function_arguments +function: test_function_arguments_python3 +class TestUnusedAssignment +TestUnusedAssignment.test_unusedVariable + +function: test_unusedUnderscoreVariable +function: test_unusedVariableAsLocals +function: test_unusedVariableNoLocals +function: test_unusedReassignedVariable +function: test_variableUsedInLoop +function: test_assignToGlobal +function: test_assignToNonlocal +function: test_assignToMember +function: test_assignInForLoop +function: test_assignInListComprehension +function: test_generatorExpression +function: test_assignmentInsideLoop +function: test_tupleUnpacking +function: test_listUnpacking +function: test_closedOver +function: test_doubleClosedOver +function: test_tracebackhideSpecialVariable +function: test_debuggerskipSpecialVariable +function: test_ifexp +function: test_if_tuple +function: test_withStatementNoNames +function: test_withStatementSingleName +function: test_withStatementAttributeName +function: test_withStatementSubscript +function: test_withStatementSubscriptUndefined +function: test_withStatementTupleNames +function: test_withStatementListNames +function: test_withStatementComplicatedTarget +function: test_withStatementSingleNameUndefined +function: test_withStatementTupleNamesUndefined +function: test_withStatementSingleNameRedefined +function: test_withStatementTupleNamesRedefined +function: test_withStatementUndefinedInside +function: test_withStatementNameDefinedInBody +function: test_withStatementUndefinedInExpression +function: test_dictComprehension +function: test_setComprehensionAndLiteral +function: test_exceptionUsedInExcept +function: test_exceptionUnusedInExcept +function: test_exception_unused_in_except_star +function: test_exceptionUnusedInExceptInFunction +function: test_exceptWithoutNameInFunction +function: test_exceptWithoutNameInFunctionTuple +function: test_augmentedAssignmentImportedFunctionCall +function: test_assert_without_message +function: test_assert_with_message +function: test_assert_tuple +function: test_assert_tuple_empty +function: test_assert_static +function: test_yieldFromUndefined +function: test_f_string +function: test_assign_expr +function: test_assign_expr_generator_scope +function: test_assign_expr_generator_scope_reassigns_parameter +function: test_assign_expr_nested +class TestStringFormatting +TestStringFormatting.test_f_string_without_placeholders + +function: test_invalid_dot_format_calls +function: test_invalid_percent_format_calls +function: test_ok_percent_format_cannot_determine_element_count +class TestAsyncStatements +TestAsyncStatements.test_asyncDef + +function: test_asyncDefAwait +function: test_asyncDefUndefined +function: test_asyncFor +function: test_asyncForUnderscoreLoopVar +function: test_loopControlInAsyncFor +function: test_loopControlInAsyncForElse +function: test_asyncWith +function: test_asyncWithItem +function: test_matmul +function: test_formatstring +function: test_raise_notimplemented +class TestIncompatiblePrintOperator +TestIncompatiblePrintOperator.test_valid_print + +function: test_invalid_print_when_imported_from_future +function: test_print_augmented_assign +function: test_print_function_assignment +function: test_print_in_lambda +function: test_print_returned_in_function +function: test_print_as_condition_test + +@clean test_type_annotations.py +class TestTypeAnnotations +TestTypeAnnotations.test_typingOverload + +function: test_typingExtensionsOverload +function: test_typingOverloadAsync +function: test_overload_with_multiple_decorators +function: test_overload_in_class +function: test_aliased_import +function: test_not_a_typing_overload +function: test_variable_annotations +function: test_variable_annotation_references_self_name_undefined +function: test_TypeAlias_annotations +function: test_annotating_an_import +function: test_unused_annotation +function: test_unused_annotation_in_outer_scope_reassigned_in_local_scope +function: test_unassigned_annotation_is_undefined +function: test_annotated_async_def +function: test_postponed_annotations +function: test_type_annotation_clobbers_all +function: test_return_annotation_is_class_scope_variable +function: test_return_annotation_is_function_body_variable +function: test_positional_only_argument_annotations +function: test_partially_quoted_type_annotation +function: test_partially_quoted_type_assignment +function: test_nested_partially_quoted_type_assignment +function: test_quoted_type_cast +function: test_type_cast_literal_str_to_str +function: test_quoted_type_cast_renamed_import +function: test_quoted_TypeVar_constraints +function: test_quoted_TypeVar_bound +function: test_literal_type_typing +function: test_literal_type_typing_extensions +function: test_annotated_type_typing_missing_forward_type +function: test_annotated_type_typing_missing_forward_type_multiple_args +function: test_annotated_type_typing_with_string_args +function: test_annotated_type_typing_with_string_args_in_union +function: test_literal_type_some_other_module +function: test_literal_union_type_typing +function: test_deferred_twice_annotation +function: test_partial_string_annotations_with_future_annotations +function: test_forward_annotations_for_classes_in_scope +function: test_idomiatic_typing_guards +function: test_typing_guard_for_protocol +function: test_typednames_correct_forward_ref +function: test_namedtypes_classes +function: test_variadic_generics +function: test_type_statements +function: test_type_parameters_functions +function: test_type_parameters_do_not_escape_function_scopes +function: test_type_parameters_classes +function: test_type_parameters_do_not_escape_class_scopes +function: test_type_parameters_TypeVarTuple +function: test_type_parameters_ParamSpec + +@clean test_undefined_names.py +class Test +Test.test_undefined +Test.test_definedInListComp +Test.test_undefinedInListComp + +function: test_undefinedExceptionName +function: test_namesDeclaredInExceptBlocks +function: test_undefinedExceptionNameObscuringLocalVariable +function: test_undefinedExceptionNameObscuringLocalVariable2 +function: test_undefinedExceptionNameObscuringLocalVariableFalsePositive1 +function: test_delExceptionInExcept +function: test_undefinedExceptionNameObscuringLocalVariableFalsePositive2 +function: test_undefinedExceptionNameObscuringGlobalVariable +function: test_undefinedExceptionNameObscuringGlobalVariable2 +function: test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1 +function: test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2 +function: test_functionsNeedGlobalScope +function: test_builtins +function: test_builtinWindowsError +function: test_moduleAnnotations +function: test_magicGlobalsFile +function: test_magicGlobalsBuiltins +function: test_magicGlobalsName +function: test_magicGlobalsPath +function: test_magicModuleInClassScope +function: test_magicQualnameInClassScope +function: test_globalImportStar +function: test_definedByGlobal +function: test_definedByGlobalMultipleNames +function: test_globalInGlobalScope +function: test_global_reset_name_only +function: test_unused_global +function: test_del +function: test_delGlobal +function: test_delUndefined +function: test_delConditional +function: test_delConditionalNested +function: test_delWhile +function: test_delWhileTestUsage +function: test_delWhileNested +function: test_globalFromNestedScope +function: test_laterRedefinedGlobalFromNestedScope +function: test_laterRedefinedGlobalFromNestedScope2 +function: test_intermediateClassScopeIgnored +function: test_doubleNestingReportsClosestName +function: test_laterRedefinedGlobalFromNestedScope3 +function: test_undefinedAugmentedAssignment +function: test_nestedClass +function: test_badNestedClass +function: test_definedAsStarArgs +function: test_definedAsStarUnpack +function: test_usedAsStarUnpack +function: test_unusedAsStarUnpack +function: test_keywordOnlyArgs +function: test_keywordOnlyArgsUndefined +function: test_annotationUndefined +function: test_metaClassUndefined +function: test_definedInGenExp +function: test_undefinedInGenExpNested +function: test_undefinedWithErrorHandler +function: test_definedInClass +function: test_definedInClassNested +function: test_undefinedInLoop +function: test_definedFromLambdaInDictionaryComprehension +function: test_definedFromLambdaInGenerator +function: test_undefinedFromLambdaInDictionaryComprehension +function: test_undefinedFromLambdaInComprehension +function: test_dunderClass +class NameTests +NameTests.test_impossibleContext + + + + +--- changed + + + + +--- classes + + + + + +--- recent + +Found 7:in_scope + + + + + + + + + +--- not used +function: counter + + + + +Found 22:handleChildren + + + + + + + + + + + + + + + + + + + + + + + + +*** play with _FieldsOrder + +--- PR + + + + +@language python +"""Recursively import all python files in a directory and clean the result.""" +@tabwidth -4 # For a better match. +g.cls() + +# dir_ = r'C:\Python\Python3.12\Lib\site-packages\coverage' +dir_ = r'C:\Repos\ekr-fork-pyflakes' + +c.recursiveImport( + dir_=dir_, + kind = '@clean', # '@auto', '@clean', '@nosent','@file', + recursive = True, + safe_at_file = False, + # '.html', '.js', '.json', '.py', '.rs', '.svg', '.ts', '.tsx'] + # '.codon', '.cpp', '.cc', '.el', '.scm', + theTypes = ['.py',], + verbose = False, +) +if 1: + last = c.lastTopLevel() + last.expand() + if last.hasChildren(): + last.firstChild().expand() + c.redraw(last) +print('Done') + + + +vertical or horizontal + +myLeoSettings.leo: vertical + + +@language rest +@wrap + +See #3456. + + + + +# leonine + + + + + +True: (Recommended) Make a "Recovered Nodes" node whenever +Leo reads a file that has been changed outside of Leo. + + + + + + + + +'''(ekr.leo) Clean newlines from EKR text.''' +s = p.b.replace('\n\n','\nPARA\n') +s = s.replace('\n',' ') +s = s.replace(' PARA ','\n\n') +s = s.replace(' ',' ') +p.b = s + + + + + + +Set to True to enable node appearance modifications +See tree-declutter-patterns + +# **Decluttering** replaces controls custom formatting of headlines, including: + +# - Hiding or changing headline text, +# - Adding icons to headlines, +# - Changing the styling of headlines. + +# @bool tree-declutter must be True to enable decluttering. + +# blank lines and lines starting with '#' are ignored. +# See the children of this node for details. + +RULE ^@file (.*) +REPLACE \1 +ICON file_icons/file_file.png + +# **Decluttering** replaces controls custom formatting of headlines, including: + +# - Hiding or changing headline text, +# - Adding icons to headlines, +# - Changing the styling of headlines. + +# Decluttering is *inactive* when you are editing a headline. + +# Decluttering is *completely optional*. To enable decluttering, use:: + + # @bool tree-declutter = True + +# Decluttering is controlled by **decluttering rulesets**. +# You specify decluttering rulesets in the body text of:: + + # @data tree-declutter-patterns + +# As usual with @data nodes: + +# - Blank lines and lines starting with '#' are ignored. +# - You may organize the text of the @data node using child nodes. + +# Each ruleset consists of a list of lines: + +# - The first line is a **rule line**, containing a **find pattern**. +# - The second line is a **replacement line**. +# - The ruleset ends with zero or more **style lines**. + +# Find patterns are `regular expressions <https://docs.python.org/2/library/re.html>`_. +# Decluttering affects only those headlines that match a rule pattern. + +# The following section shows some example rulesets. Later sections discuss decluttering commands, patterns and styles in more detail. + +# All rulesets start with a **rule line** of the form:: + + # RULE <regular expression> + +# The ruleset matches a headline if and only if the regular expression matches. Matches can start anywhere in the headline. Leo first attempts to a match using re.match. If that doesn't work, Leo tries re.search. + +# A **replacement line** must follow the rule line. Here are the valid forms:: + + # REPLACE <substitution expression> + # REPLACE-HEAD + # REPLACE-TAIL + # REPLACE-REST + +# - REPLACE replaces the headline by the value of the substitution expression. For example:: + + # REPLACE \1 + + # matches replaces the headline by the first matched regex group. + +# - REPLACE-HEAD replaces replace the headline by the text that precedes the matched text. + +# - REPLACE-TAIL replaces the headline by the text that follows the matched text. + +# - REPLACE-REST replaces the headline by everything except the matched text. + +g.cls() + +print('===== Start =====') + +class CreateDecorators: + ''' + A class to create decorators from tables in getPublicCommands. + + Note: the node "Found: getPublicCommands" must exist. + ''' + def __init__(self,c,make_changes): + self.c = c + self.fixups = self.create_fixups() + self.n = 0 + self.n_fail = 0 + self.make_changes=make_changes + self.suppress = [ + 'c.frame.body and c.frame.body.addEditor', + 'cls','cloneFindParents','cycleTabFocus', + 'k and k.keyboardQuit', + 'menuShortcutPlaceHolder','removeBlankLines', + 'saveBuffersKillLeo', + ] + @others + +CreateDecorators(c,make_changes=False).run() + +# Leo applies style lines only if they appear in a ruleset that matches a headline. +# Style lines do the following... + +# Add an icon to the headline:: + + # ICON path/to/icon + +# Set the background or foreground color to a color number or names:: + + # BG #FF8800 + # FG @solarized-magenta + +# Set the font to a given font name:: + + # Font Times + +# Set the font size in pixels (PX) or points (PT):: + + # PX 40 + # PT 16 + +# Enable or disable italics:: + + # ITALIC 0 + # ITALIC 1 + +# Set the font weight to one of Light, Normal, DemiBold, Bold, Black:: + + # WEIGHT DemoBold + +# Add Icon to folders and remove /-/ +RULE ^/(.*)/$ +REPLACE \1 +ICON file_icons/folder.png + +# Add icon to path folders and remove @path +RULE ^@path (.*) +REPLACE \1 +ICON file_icons/folder_path.png + +# Add Icon to removed folders and remove */-/* +RULE ^\*/(.*)/\*$ +REPLACE \1 +ICON file_icons/folder_removed.png + +# Add Icon to removed files and remove *-* but not **-** +RULE ^\*([^\*/]*[^\*]*[^\*/]*)\*$ +REPLACE \1 +ICON file_icons/removed.png + +# if the node name starts with 'peacock node DEMO', make a mess of it +RULE ^(peacock node DEMO) +REPLACE LOOK: \1 +ICON Tango/16x16/emotes/face-grin.png +ICON Tango/16x16/emotes/face-wink.png +FG @solarized-magenta +BG white +FONT Times +PX 40 +ITALIC 1 +WEIGHT Bold + +# RULE :([\w_@]+:)+\s*$ +# REPLACE-HEAD + +# remove @clean etc. and use an icon +RULE ^@clean (.*) +REPLACE \1 +ICON file_icons/file_clean.png + +RULE ^@auto (.*) +REPLACE \1 +ICON file_icons/file_auto.png + +RULE ^@edit (.*) +REPLACE \1 +ICON file_icons/file_edit.png + +RULE ^@asis (.*) +REPLACE \1 +ICON file_icons/file_asis.png + +RULE ^@nosent (.*) +REPLACE \1 +ICON file_icons/file_nosent.png + +RULE ^@file (.*) +REPLACE \1 +ICON file_icons/file_file.png + +# show the last part of long filenames +RULE ^.{1,1000}/(.{20}) +REPLACE …/\1 + + + + + +def create_d(self,lines,publicCommands): + '''Create a dict. keys are method names; values are command names.''' + trace = False + if trace: + print('') + g.trace(publicCommands.h) + d = {} + for s in lines: + aList = s.split() + if len(aList) > 2: + aList = [aList[0],' '.join(aList[1:])] + c_name,f_name = aList[0].strip(),aList[1].strip() + if ' ' not in f_name: + f_name = f_name.split('.')[-1] + # if '(' in f_name: + # f_name = f_name[:f_name.find('(')] + if trace: g.trace('%45s %s' % (c_name,f_name)) + d [f_name] = c_name + return d + + + + + +Only supported with the mod_tempfname.py plugin. + +True: The plugin will store temporary files utilizing cleaner +file names (no unique number is appended to the node's headline text). +Unique temporary directory paths are used to insure unique files are +created by creating temporary directories reflecting each node's ancestor +nodes in the Leo outline. Note: Do not have multiple sibling nodes (nodes +having the same parent node) in Leo with the same headline text. There will +be a conflict if both are opened in an external editor at the same time. + +False: The plugin will store temporary files with an appended +unique number to insure unique temporary filenames. + +True: check all @<file> nodes in the outline for changes in corresponding external files. + + + + +def create_decorator(self,c_name,f_name,root): + ''' + Search root for a definition of f_name. + If found, insert @cmd(f_name) before the definition. + ''' + # g.trace('%45s %s' % (c_name,f_name)) + trace = False + found = False + decorator = "@cmd('%s')\n" % (c_name) + for p in root.self_and_subtree(): + changed,result = False,[] + for s in g.splitLines(p.b): + if g.match_word(s,0,'def ' + f_name): + if found: + if f_name not in self.suppress: + g.trace('duplicate def',f_name) + else: + changed,found = True,True + result.append(decorator) + # print('%s%s' % (decorator,s)) + result.append(s) + # if changed and self.make_changes: + # new_body = ''.join(result) + # # use git as our undo :-) + # p.b = new_body + return found + + +It is *strange* to set this to True! +@language rest + +To test #2041 & #2094 + +The @bool use-find-dialog and @bool minibuffer-find-mode settings comprise +a tri-state setting, as shown in this table: + +minibuffer-find-mode use-find-dialog mode: Ctrl-F puts focus in +-------------------- --------------- -------------------------- + True Ignored minibuffer + False True dialog + False False Find tab in the log pane + +*All modes* + +- Start the search with Ctrl-F (start-search). +- Enter the find pattern. +- (Optional) Use <Tab> to enter the search pattern. +- Use <Enter> to start the search. + +*dialog and find tab modes* + +- Non-functional "buttons" remind you of key bindings. + +*minibuffer mode* + +- Use Ctrl-G as always to leave the minibuffer. +- The Find tab is not made visible, but the status area shows the settings. +@language rest + +The @bool use-find-dialog and @bool minibuffer-find-mode settings comprise +a tri-state setting, as shown in this table: + +minibuffer-find-mode use-find-dialog mode: Ctrl-F puts focus in +-------------------- --------------- -------------------------- + True Ignored minibuffer + False True dialog + False False Find tab in the log pane + +*All modes* + +- Start the seas with Ctrl-F (start-search). +- Enter the find pattern. +- (Optional) Use <Tab> to enter the search pattern. +- Use <Enter> to start the search. + +*dialog and find tab modes* + +- Non-functional "buttons" remind you of key bindings. + +*minibuffer mode* + +- Use Ctrl-G as always to leave the minibuffer. +- The Find tab is not made visible, but the status area shows the settings. +Added on-popover to import-html-tags (for leovue) +# lowercase html tags, one per line. +# *** Add ons-popover tag for LeoVue. + +a +abbr +acronym +address +applet +area +b +base +basefont +bdo +big +blockquote +body +br +button +caption +center +cite +code +col +colgroup +dd +del +dfn +dir +div +dl +dt +em +fieldset +font +form +frame +frameset +head +h1 +h2 +h3 +h4 +h5 +h6 +hr +html +i +iframe +img +input +ins +kbd +label +legend +li +link +map +menu +meta +noframes +noscript +object +ol +ons-popover +optgroup +option +p +param +pre +q +s +samp +script +select +small +span +strike +strong +style +sub +sup +table +tbody +td +textarea +tfoot +th +thead +title +tr +tt +u +ul +var +# lowercase xml tags, one per line. + +html +body +head +div +table + +For make-stub-files +True: allow stub files to be overwritten + + +def create_decorators(self,d,root): + '''Create decorators for all items in d in root's tree.''' + # print('***** %s' % root.h) + if root.h in self.fixups: + roots = [] + aList = self.fixups.get(root.h) + for root2_h in aList: + root2 = g.findNodeAnywhere(self.c,root2_h) + if root2: + # g.trace(root.h,'=====>',root2.h) + roots.append(root2) + else: + g.trace('===== not found',root2_h) + else: + roots = [root] + for f_name in sorted(d.keys()): + found = False + for root in roots: + c_name = d.get(f_name) + found = self.create_decorator(c_name,f_name,root) + if found: break + if not found and f_name not in self.suppress: + print('===== not found: %30s %s' % (root.h,f_name)) + self.n_fail += 1 + + + + + +AstFormatter.*: str +Pattern.all_matches: Sequence +Pattern.full_balanced_match: Optional[int] +Pattern.match_balanced: int +Pattern.match_entire_string: bool +StandAloneMakeStubFile.scan_types: Dict[str, str] +StubFormatter.do_.*: str +StubTraverser.format_returns: str +StubTraverser.match_return_patterns: Tuple[bool,str] +StubTraverser.match_return_pattern: Optional[str] +StubTraverser.match_balanced: int +# Patterns to be applied to argument lists and return expressions. + +aList: Sequence +aList1: Sequence +aList2: Sequence +c: C +c1: C +c2: C +i: int +j: int +k: int +node: ast.Ast +p: P +p1: P +p2: P +s: str +s2: str +v: V +v1: V +v2: V + +aList: Sequence +controller: StandAloneMakeStubFile +fn: str +i[0-3]*: int +parser: optparse.OptionParser +node: Node +s[0-3]*: str +strict: bool + +repr(*): str +str.join(*): str +str.replace(*): str +str%(*): str +str%str: str + +.*__name__: str + +# Lines to be inserted at the start of each stub file. + +from typing import Any, Dict, Optional, Sequence, Tuple, Union +# At present, I don't understand how to tell mypy about ast.Node +# import ast +# Node = ast.Node + +Node = Any + +The directory to which stub files are written. + +# Recommended plugins, from leoSettings.leo: + +plugins_menu.py +mod_scripting.py +nav_qt.py +viewrendered.py + +# bookmarks.py +# contextmenu.py # Required by the vim.py and xemacs.py plugins. +# nodetags.py +# quicksearch.py +# todo.py +# viewrendered3.py + +### Testing + +# backlink.py +# freewin.py +# mod_autosave.py +# quickMove.py +# screenshots.py +# settings_finder.py +# rpcalc.py +# wikiview.py + +def create_fixups(self): + ''' + Return a fixup dict. + Keys are headlines for classes. + Values are new headlines of nodes containing the actual class. + ''' + return { + 'ChapterCommandsClass': ['class ChapterController'], + 'EditCommandsClass': [ + 'EditCommandsClass', + 'class Commands', + 'class LeoQtFrame', + 'class LeoBody', + ], + 'class SearchCommandsClass': ['class LeoFind (LeoFind.py)'], + 'KeyHandlerCommandsClass (add docstrings)': [ + 'class KeyHandlerClass', + 'class AutoCompleterClass', + ] + } + + + + + + +# True: show vr pane when opening a file. +# True: hide the vr pane for text-only renderings. + + +def find_class(self,p): + '''Return the position of the class enclosing p.''' + for p2 in p.parents(): + if p2.h.lower().find('class') > -1 and p2.b.find('class') > -1: + return p2 + else: + g.trace('*** no class for p.h') + return None + +# regex patterns for text to be hidden by the wikiview plugin +# Blanks lines and lines starting with '#' are comment lines. + +# Each non-comment line represents a pattern. +# Use \b# for patterns starting with '#' +# Only NON `groups` parts of the pattern in parentheses will be shown. +# The first character of the pattern (not counting \b) is the leadin character. +# The pattern will be applied only for strings starting with the leadin character. + +# UNLs + +\bunl:(//.*#.*-->).* +\bunl:(//.*#).* +\bunl:(//.*-->).* +\bunl:(//).* +\bfile:(//.*-->).* +\bhttps?:(//.*-->).* +## \bfile:(//.*-->)\S+\b +## \bhttps?:(//.*-->)\S+\b + +# regular urls + +\bhttps?:(//.*/)\w+\b +\bfile:(//.*/)\w+\b + +# restructuredText `Visible text <http://invisible.url/here>` + +(`)\S+(\s*<https?://\S+>`_) +(`)\S+(\s*<file://\S+>`_) + +# Test patterns: see http://pythex.org/ + +# unl://leoSettings.leo#@settings-->Plugins-->wikiview plugin +# unl://ekr.leo#Startup-->@settings-->@@data global-abbreviations +# unl://#Startup-->@settings-->@@data global-abbreviations +# unl://Startup-->@settings-->@@data global-abbreviations +# file://Startup-->@settings +# file://#some-->headlines-->mynode +# http://#some-->headlines-->mynode +# https://#some-->headlines-->mynode +# http://www.google.com/search +# https://www.google.com/search +# file://www.google.com/search +# `Python <https://www.python.org/>`_ +# `Python <file://www.python.org/>`_ + +Should wikiview mode be active by default? + + + + + + + +Only difference from myLeoSettings.leo + +Note: EKRWinowsDark.leo defines comment1_font + +All three @color settings work. +The @font setting does not work. + +def find_next_clone(self,p): + v = p.v + p = p.copy() + p.moveToThreadNext() + wrapped = False + while 1: + # g.trace(p.v,p.h) + if p and p.v == v: + break + elif p: + p.moveToThreadNext() + elif wrapped: + break + else: + wrapped = True + p = c.rootPosition() + return p + +Bold +Italics + +# bold keywords defined in forth-bold-words + +# Note: the default font size is 12. +rest_comment1_family = None +rest_comment1_size = 12pt +rest_comment1_slant = italic +rest_comment1_weight = None + +These must be @string settings, even though they do affect colors. +solarized blue: #268bd2 +solarized-red = #dc322f + +@nosearch + +def munge_lines(self,root,publicCommands): + '''Return munged lines of ''' + # print('') + # g.trace(root.h) + s = publicCommands.b + i,j = s.find('{'),s.find('}') + s = s[i+1:j] + # print(s) + lines = sorted([z.strip() for z in g.splitLines(s) if z.strip()]) + lines = [z for z in lines if not z.startswith('#')] + lines = [z[:z.find('#')] if z.find('#') > -1 else z for z in lines] + lines = [z.rstrip().rstrip(',') for z in lines] + lines = [z[1:] for z in lines] + lines = [z.replace("':",' ') for z in lines] + # print('\n'.join(lines)) + self.n += len(lines) + return lines + +solarized blue: #268bd2 +# Note: Use jj instead of escape to end insert mode. + +""" +Back up this .leo file. + +os.environ['LEO_BACKUP'] must be the path to an existing (writable) directory. +""" +c.backup_helper(sub_dir='ekr-pyflakes') + +print(p.gnx) + +def run(self): + '''Top-level code.''' + self.n = 0 + found = g.findNodeAnywhere(c,'Found: getPublicCommands') + assert found + for child in found.children(): + publicCommands = self.find_next_clone(child) + root = self.find_class(publicCommands) + if root: + lines = self.munge_lines(root,publicCommands) + d = self.create_d(lines,publicCommands) + self.create_decorators(d,root) + print('\n%s commands %s failed' % (self.n,self.n_fail)) + +@language python +"""Introspect""" + +# By Terry Brown. Requires Python 2.x. + +# https://groups.google.com/forum/#!msg/leo-editor/Qu2HccpC_wc/_ee11jIvAQAJ + +import types + +sub_mode = 'instance' +# 'instance' or 'class' - controls which, instance or class names, +# are put it a subnode. 'instance class' sub-nodes both. +# '' appends classes after names, not useful. + +def classname(thing): + if hasattr(thing, '__class__'): + return thing.__class__.__name__ + else: + return thing.__name__ + +if not hasattr(c.p.v, '_introspection_target'): + txt = g.app.gui.runAskOkCancelStringDialog( + c, "Introspect what", "Introspect what") + if txt is not None: + o = eval(txt) + c.p.v._introspection_target = o + c.p.h = "%s %s" % (txt, classname(o)) + +# c.p.deletePositionsInList([i.copy() for i in p.children()]) + +obj = c.p.v._introspection_target +g.es(classname(obj)) + +def show_obj(c, obj): + + inames = sorted(dir(obj)) + + things = {} + instances = [] + for iname in inames: + + if iname.startswith('__'): + continue + + o = getattr(obj, iname) + cname = classname(o) + instances.append((iname, o)) + things.setdefault(cname, []).append(instances[-1]) + + if 'instance' in sub_mode: + tnd = c.p.v.insertAsNthChild(0) + tnd.h = "<by name>" + else: + tnd = c.p.v + + instances.sort() + for iname, o in instances: + + if classname(o) == 'position': + # apparently this collapses the space-time continuum? + continue + + nd = tnd.insertAsLastChild() + + if not seen_already(tnd, nd, iname, o): + nd.h = "%s %s" % (iname, format_type(nd, o)) + nd._introspection_target = o + + if 'class' in sub_mode: + ttnd = c.p.v.insertAsNthChild(0) + ttnd.h = "<by class>" + else: + ttnd = c.p.v + + for cname in sorted(things): + + if len(things[cname]) == 1: + tnd = ttnd + else: + tnd = ttnd.insertAsLastChild() + tnd.h = "<%s>"%cname + + for iname, o in sorted(things[cname]): + + if cname == 'position': + # apparently this collapses the space-time continuum? + continue + + nd = tnd.insertAsLastChild() + if not seen_already(tnd, nd, iname, o): + show_child(nd, iname, o) + nd._introspection_target = o + +def seen_already(tnd, nd, iname, o): + + up = tnd.parents + while up: + if (hasattr(up[0], '_introspection_target') and + up[0]._introspection_target is o): + break + up = up[0].parents + else: + return False + + nd.h = "[%s %s]" % (classname(o), iname) + pos = c.vnode2position(up[0]) + nd.b = pos.get_UNL(with_file=True, with_proto=True) + + return True + +def show_child(nd, iname, o): + + nd._introspection_target = o + nd.h = "%s %s" % (format_type(nd, o), iname) + +docable = ( + types.ClassType, types.MethodType, types.UnboundMethodType, + types.BuiltinFunctionType, types.BuiltinMethodType, +) + +def format_type(nd, o): + + if isinstance(o, docable): + if hasattr(o, '__doc__'): + nd.b = o.__doc__ + + if isinstance(o, (str, unicode)): + nd.b = o + return "%s '%s'" % (classname(o), o[:20]) + elif isinstance(o, bool): + return "%s %s" % (classname(o), 'T' if o else 'F') + elif isinstance(o, (int, float)): + return "%s %s" % (classname(o), o) + elif isinstance(o, (tuple, list, dict)): + return "%s %s" % (classname(o), len(o)) + else: + return classname(o) + +def show_list(c, list_): + + if len(list_) > 100: + nd = c.p.v.insertAsLastChild() + nd.h = "<%s of %d items truncated>" % len(list_.__class__.__name__, list_) + + if len(list_) == 0: + nd = c.p.v.insertAsLastChild() + nd.h = "<%s of 0 items>" % list_.__class__.__name__ + + for n, i in enumerate(list_[:100]): + nd = c.p.v.insertAsLastChild() + show_child(nd, '', i) + nd.h = "%d: %s" % (n, nd.h) + nd._introspection_target = i + +def show_dict(c, dict_): + + if len(dict_) > 100: + nd = c.p.v.insertAsLastChild() + nd.h = "<dict of %d items truncated>" % len(dict_) + + if len(dict_) == 0: + nd = c.p.v.insertAsLastChild() + nd.h = "<dict of 0 items>" + + keys = dict_.keys() + keys.sort() + + for k in keys[:100]: + nd = c.p.v.insertAsLastChild() + i = dict_[k] + show_child(nd, '', i) + nd.h = "%s: %s" % (k, nd.h) + nd._introspection_target = i + +dispatch = { + list: show_list, + tuple: show_list, + dict: show_dict, +} + +func = dispatch.get(type(obj), show_obj) + +func(c, obj) + +c.p.expand() +c.redraw() + +'''@button (ekr.leo) Join the lines, with ; separators''' +# No longer needed on Windows 10 +w = c.frame.body.widget +aList = [z.rstrip() for z in p.b.split('\n')] +p.b = ';'.join(aList) +c.bodyWantsFocusNow() + +g.cls() + +# Changed files: +# leoApp.py +# leoAtFile.py +# leoCommands.py +# leoFileCommands.py +# leoFrame.py +# leoUndo.py +# qt_frame.py + +make_changes = True + # True, actually make the change + +class CreateDecorators: + ''' + A class to create decorators from tables in getPublicCommands. + + Note: the node "Found: getPublicCommands" must exist. + ''' + def __init__(self): + self.n = 0 + self.n_fail = 0 + self.s = self.define_s() + @others + +CreateDecorators().run() + +def create_d(self,lines): + '''Create a dict. keys are method names; values are command names.''' + trace = False + d = {} + for s in lines: + aList = s.split() + if len(aList) > 2: + aList = [aList[0],' '.join(aList[1:])] + c_name,f_name = aList[0].strip(),aList[1].strip() + if ' ' not in f_name: + f_name = f_name.split('.')[-1] + # if '(' in f_name: + # f_name = f_name[:f_name.find('(')] + if trace: g.trace('%45s %s' % (c_name,f_name)) + d [f_name] = c_name + return d + +h = '--- @edit files' +root = g.findTopLevelNode(c, h) +start_s = 'LilyPond is free software:' +end_s = 'If not, see <http://www.gnu.org/licenses/>.' +for p in root.children(): + s = p.b + i = s.find(start_s) + j = s.find(end_s) + if -1 < i < j: + s = s[:i] + s[j + len(end_s):] + p.b = s + else: + print('not found', p.h) +print('done') +def create_decorator(self,c_name,f_name,root): + ''' + Search root for a definition of f_name. + If found, insert @cmd(f_name) before the definition. + ''' + trace = True + found = False + decorator = "@cmd('%s')\n" % (c_name) + for p in root.self_and_subtree(): + changed,result = False,[] + for s in g.splitLines(p.b): + if g.match_word(s,0,'def ' + f_name): + if found: + if f_name not in self.suppress: + g.trace('duplicate def',f_name) + else: + changed,found = True,True + result.append(decorator) + # print('%s%s' % (decorator,s)) + result.append(s) + if changed and make_changes: + new_body = ''.join(result) + print('%40s %s' % (p.h[:40],decorator.rstrip())) + # use git as our undo :-) + # p.b = new_body + return found + +def create_decorators(self,d): ### ,root): + '''Create decorators for all items in d in root's tree.''' + table = ( + 'class Commands', # c. + 'class LeoQtFrame', # f. + 'class LeoFrame', # f. + 'class LeoApp', # g.app. + '@file leoAtFile.py', # c.atFileCommands + '@file leoFileCommands.py', # c.fileCommands + 'class Undoer', # c.undoer + ) + roots = [] + for h in table: + root = g.findNodeAnywhere(c,h) + assert root,h + roots.append(root) + for f_name in sorted(d.keys()): + found = False + for root in roots: + c_name = d.get(f_name) + found = self.create_decorator(c_name,f_name,root) + if found: break + if not found and f_name not in self.suppress: + print('===== not found: %s' % (f_name)) + self.n_fail += 1 + +# 'check-all-python-code': c.checkAllPythonCode, +# 'check-python-code': c.checkPythonCode, +# 'extract-python-method': c.extractPythonMethod, +# 'extract-section': c.extractSection, +# 'import-at-file': c.importAtFile, +# 'import-at-root': c.importAtRoot, +# 'import-cweb-files': c.importCWEBFiles, +# 'import-derived-file': c.importDerivedFile, +# 'import-flattened-outline': c.importFlattenedOutline, +# 'import-noweb-files': c.importNowebFiles, +# 'mark-changed-roots': c.markChangedRoots, +# 'mark-clones': c.markClones, +# 'open-compare-window': c.openCompareWindow, +# 'open-online-tutorial': c.leoTutorial, +# 'reformat-body': c.reformatBody, # 2013/10/02. +def define_s(self): + return ''' +'abort-edit-headline': f.abortEditLabelCommand, +'about-leo': c.about, +'add-comments': c.addComments, +'beautify': c.beautifyPythonCode, +'beautify-all': c.beautifyAllPythonCode, +'beautify-c': c.beautifyCCode, +'beautify-tree': c.beautifyPythonTree, +'cascade-windows': f.cascade, +'check-derived-file': c.atFileCommands.checkDerivedFile, +'check-leo-file': c.fileCommands.checkLeoFile, +'check-outline': c.fullCheckOutline, +'clean-recent-files': c.cleanRecentFiles, +'clear-recent-files': c.clearRecentFiles, +'clone-node': c.clone, +'clone-node-to-last-node': c.cloneToLastNode, +'close-window': c.close, +'contract-all': c.contractAllHeadlines, +'contract-all-other-nodes': c.contractAllOtherNodes, +'contract-node': c.contractNode, +'contract-or-go-left': c.contractNodeOrGoToParent, +'contract-parent': c.contractParent, +'convert-all-blanks': c.convertAllBlanks, +'convert-all-tabs': c.convertAllTabs, +'convert-blanks': c.convertBlanks, +'convert-tabs': c.convertTabs, +'copy-node': c.copyOutline, +'copy-text': f.copyText, +'cut-node': c.cutOutline, +'cut-text': f.cutText, +'de-hoist': c.dehoist, +'delete-comments': c.deleteComments, +'delete-node': c.deleteOutline, +'demote': c.demote, +'dump-outline': c.dumpOutline, +'edit-headline': c.editHeadline, +'end-edit-headline': f.endEditLabelCommand, +'equal-sized-panes': f.equalSizedPanes, +'execute-script': c.executeScript, +'exit-leo': g.app.onQuit, +'expand-all': c.expandAllHeadlines, +'expand-all-subheads': c.expandAllSubheads, +'expand-ancestors-only': c.expandOnlyAncestorsOfNode, +'expand-and-go-right': c.expandNodeAndGoToFirstChild, +'expand-next-level': c.expandNextLevel, +'expand-node': c.expandNode, +'expand-or-go-right': c.expandNodeOrGoToFirstChild, +'expand-prev-level': c.expandPrevLevel, +'expand-to-level-1': c.expandLevel1, +'expand-to-level-2': c.expandLevel2, +'expand-to-level-3': c.expandLevel3, +'expand-to-level-4': c.expandLevel4, +'expand-to-level-5': c.expandLevel5, +'expand-to-level-6': c.expandLevel6, +'expand-to-level-7': c.expandLevel7, +'expand-to-level-8': c.expandLevel8, +'expand-to-level-9': c.expandLevel9, +'export-headlines': c.exportHeadlines, +'extract': c.extract, +'extract-names': c.extractSectionNames, +'find-next-clone': c.findNextClone, +'flatten-outline': c.flattenOutline, +'flatten-outline-to-node': c.flattenOutlineToNode, +'go-back': c.goPrevVisitedNode, +'go-forward': c.goNextVisitedNode, +'goto-first-node': c.goToFirstNode, +'goto-first-sibling': c.goToFirstSibling, +'goto-first-visible-node': c.goToFirstVisibleNode, +'goto-last-node': c.goToLastNode, +'goto-last-sibling': c.goToLastSibling, +'goto-last-visible-node': c.goToLastVisibleNode, +'goto-next-changed': c.goToNextDirtyHeadline, +'goto-next-clone': c.goToNextClone, +'goto-next-history-node': c.goToNextHistory, +'goto-next-marked': c.goToNextMarkedHeadline, +'goto-next-node': c.selectThreadNext, +'goto-next-sibling': c.goToNextSibling, +'goto-next-visible': c.selectVisNext, +'goto-parent': c.goToParent, +'goto-prev-history-node': c.goToPrevHistory, +'goto-prev-node': c.selectThreadBack, +'goto-prev-sibling': c.goToPrevSibling, +'goto-prev-visible': c.selectVisBack, +'hide-invisibles': c.hideInvisibles, +'hoist': c.hoist, +'import-file': c.importAnyFile, +'indent-region': c.indentBody, +'insert-body-time': c.insertBodyTime, +'insert-child': c.insertChild, +'insert-node': c.insertHeadline, +'insert-node-before': c.insertHeadlineBefore, +'mark': c.markHeadline, +'mark-changed-items': c.markChangedHeadlines, +'mark-subheads': c.markSubheads, +'match-brackets': c.findMatchingBracket, +'minimize-all': f.minimizeAll, +'move-outline-down': c.moveOutlineDown, +'move-outline-left': c.moveOutlineLeft, +'move-outline-right': c.moveOutlineRight, +'move-outline-up': c.moveOutlineUp, +'new': c.new, +'open-cheat-sheet-leo': c.openCheatSheet, +'open-leoDocs-leo': c.leoDocumentation, +'open-leoPlugins-leo': c.openLeoPlugins, +'open-leoSettings-leo': c.openLeoSettings, +'open-local-settings': c.selectAtSettingsNode, +'open-myLeoSettings-leo': c.openMyLeoSettings, +'open-offline-tutorial': f.leoHelp, +'open-online-home': c.leoHome, +'open-online-toc': c.openLeoTOC, +'open-online-tutorials': c.openLeoTutorials, +'open-online-videos': c.openLeoVideos, +'open-outline': c.open, +'open-python-window': c.openPythonWindow, +'open-quickstart-leo': c.leoQuickStart, +'open-scripts-leo': c.openLeoScripts, +'open-users-guide': c.openLeoUsersGuide, +'open-with': c.openWith, +'outline-to-cweb': c.outlineToCWEB, +'outline-to-noweb': c.outlineToNoweb, +'paste-node': c.pasteOutline, +'paste-retaining-clones': c.pasteOutlineRetainingClones, +'paste-text': f.pasteText, +'pretty-print-all-python-code': c.prettyPrintAllPythonCode, +'pretty-print-python-code': c.prettyPrintPythonCode, +'promote': c.promote, +'read-at-auto-nodes': c.readAtAutoNodes, +'read-at-file-nodes': c.readAtFileNodes, +'read-at-shadow-nodes': c.readAtShadowNodes, +'read-file-into-node': c.readFileIntoNode, +'read-outline-only': c.readOutlineOnly, +'redo': c.undoer.redo, +'reformat-paragraph': c.reformatParagraph, +'refresh-from-disk': c.refreshFromDisk, +'remove-sentinels': c.removeSentinels, +'resize-to-screen': f.resizeToScreen, +'revert': c.revert, +'save-all': c.saveAll, +'save-file': c.save, +'save-file-as': c.saveAs, +'save-file-as-unzipped': c.saveAsUnzipped, +'save-file-as-zipped': c.saveAsZipped, +'save-file-to': c.saveTo, +'set-colors': c.colorPanel, +'set-font': c.fontPanel, +'settings': c.preferences, +'show-invisibles': c.showInvisibles, +'sort-children': c.sortChildren, +'sort-recent-files': c.sortRecentFiles, +'sort-siblings': c.sortSiblings, +'tangle': c.tangle, +'tangle-all': c.tangleAll, +'tangle-marked': c.tangleMarked, +'toggle-active-pane': f.toggleActivePane, +'toggle-angle-brackets': c.toggleAngleBrackets, +'toggle-invisibles': c.toggleShowInvisibles, +'toggle-sparse-move': c.toggleSparseMove, +'toggle-split-direction': f.toggleSplitDirection, +'undo': c.undoer.undo, +'unformat-paragraph': c.unformatParagraph, +'unindent-region': c.dedentBody, +'unmark-all': c.unmarkAll, +'untangle': c.untangle, +'untangle-all': c.untangleAll, +'untangle-marked': c.untangleMarked, +'weave': c.weave, +'write-at-auto-nodes': c.atFileCommands.writeAtAutoNodes, +'write-at-file-nodes': c.fileCommands.writeAtFileNodes, +'write-at-shadow-nodes': c.fileCommands.writeAtShadowNodes, +'write-dirty-at-auto-nodes': c.atFileCommands.writeDirtyAtAutoNodes, +'write-dirty-at-file-nodes': c.fileCommands.writeDirtyAtFileNodes, +'write-dirty-at-shadow-nodes': c.fileCommands.writeDirtyAtShadowNodes, +'write-file-from-node': c.writeFileFromNode, +'write-missing-at-file-nodes': c.fileCommands.writeMissingAtFileNodes, +'write-outline-only': c.fileCommands.writeOutlineOnly, +''' + +def munge_lines(self,s): + '''Return munged lines of s. ''' + lines = sorted([z.strip() for z in g.splitLines(s) if z.strip()]) + lines = [z for z in lines if not z.startswith('#')] + lines = [z[:z.find('#')] if z.find('#') > -1 else z for z in lines] + lines = [z.rstrip().rstrip(',') for z in lines] + lines = [z[1:] for z in lines] + lines = [z.replace("':",' ') for z in lines] + self.n += len(lines) + return lines + +def run(self): + '''Top-level code.''' + lines = self.munge_lines(self.s) + d = self.create_d(lines) + self.create_decorators(d) + print('%s commands %s failed' % (self.n,self.n_fail)) + +'''@button (ekr.leo) print the gnx.''' +# g.cls() +print('timestamp: %s lastIndex: %s' % (g.app.nodeIndices.timeString,g.app.nodeIndices.lastIndex)) +print('gnxs: -----') +for p in c.p.self_and_subtree(): + print('%s %s' % (p.v.gnx,p.h)) +print('uAs: -----') +for p in c.p.self_and_subtree(): + if p.v.u: + print('%s %s' % (p.v.u,p.h)) +# print('done') + +c.k.simulateCommand('print-style-sheet') +d = p.v.u +if d: + for key in sorted(d): + print('%10s %s' % (key, d.get(key))) +print(p.h) +assert False +print('hi: %s' % p.h) +'''@button (ekr.leo) set the ua to 'test-head' ''' + +p.v.u = {'test-head':p.h} +p.setDirty() +c.setChanged(True) +c.redraw() + +'''@button (ekr.leo) Split the body text at semicolons''' +# No longer needed on Windows 10 +w = c.frame.body.widget +p.b = '\n'.join(p.b.split(';')) +c.bodyWantsFocusNow() +# w.selectAllText() + +c.testManager.runTimerOnNode(p,count=100000) + +g.app.debug_app = not g.app.debug_app +g.app.debug_widgets = not g.app.debug_widgets +print('g.app.debug_app: %s' % g.app.debug_app) +print('g.app.debug_widgets: %s' % g.app.debug_widgets) +g.cls() + +import unittest +# import pyflakes +# print(pyflakes) +import os +import sys +path = g.os_path_finalize_join(os.curdir, '..') +print(path) +if 0: + import importlib + import importlib.util + # importlib.invalidate_caches() + pyflakes = importlib.util.spec_from_file_location("pyflakes", path) + # importlib.import_module('pyflakes', path) + +else: + if 'pyflakes' in sys.modules: + del sys.modules['pyflakes'] + if sys.path[0] != path: + sys.path.insert(0, path) + import pyflakes +print(pyflakes) +if 1: + from pyflakes.test import test_api, test_doctests, test_imports, test_other + from pyflakes.test import test_return_with_arguments_inside_generator + from pyflakes.test import test_undefined_names + tests = ( + test_api, + test_doctests, + test_imports, test_other, + test_return_with_arguments_inside_generator, + test_undefined_names, + ) + loader = unittest.TestLoader() + suite = unittest.TestSuite() + for module in tests: + suite.addTest(loader.loadTestsFromModule(module)) + unittest.TextTestRunner(verbosity=1).run(suite) +print('Test') +d = { +'annotate': {'priority': 2, 'prisetdate': '2017-10-22'} + 'icons': [{'yoffset': 0, + 'where': 'beforeHeadline', + 'file': 'C:\\leo.repo\\leo-editor\\leo\\Icons\\cleo\\pri2.png', + 'type': 'file', + 'xoffset': 2, + 'relPath': 'cleo\\pri2.png', + 'on': 'vnode', + 'xpad': 1, + 'cleoIcon': '1'}] +} + +g.printDict(p.u) + +print(c.p.numberOfChildren()) + + + + + +# This node contains the commands needed to execute a program in a particular language. + +# Format: language-name: command + +# Create a temporary file if c.p is not any kind of @<file> node. + +# Compute the final command as follows: + +# 1. If command contains <FILE>, replace <FILE> with the full path to the external file. +# 2. If command contains <NO-FILE>, just remove <NO-FILE>. +# 3. Otherwise, append the full path to the external file to the command. + +go: go run . <NO-FILE> +python: python +rust: rustc + +# This node contains the regex pattern to determine the line number in error messages. +# Format: language-name: regex pattern +# +# Patterns must define two groups, in either order: +# One group, containing only digits, defines the line number. +# The other group defines the file name. + +go: ^\s*(.*):([0-9]+):([0-9]+):.+$ +python: ^\s*File "(.+)", line ([0-9]+), in .+$ +rust: ^\s*--> (.+):([0-9]+):([0-9]+)\s*$ +clone-to-at-spot +restart-leo +execute-script +backup + +# import-to-indented-lisp +# import-to-indented-typescript +# import-to-indented-c + +# refresh-from-disk +# pylint +# show-plugin-handlers +# merge-node-with-next-node +# merge-node-with-prev-node +# beautify-files + + + + + + + + + + +g.cls() +print('rclick hi: %s' % c.p.h) +print('dir()', dir()) +print(script_args) +print(script_gnx) + + +# The headline must be: @outline-data tree-abbreviations + +# A list tree abbreviation names. + +# For each abbreviation name, there should be corresponding child node, +# the **abbreviation node** whose headline matches the abbreviation name. + +# When a tree abbreviation fires, Leo pastes all the descendants of +# the abbreviation node as the last children of the presently selected node. + +importer;; +per-commander-plugin;; +demo;; + + + + +''' +A template for demonstrations based on plugins/demo.py. +The demo;; abbreviation will create this tree. +''' +<< imports >> +@others +# Use the *same* command/key binding for demo-start and demo.next. +try: + if getattr(g.app, 'demo', None): + g.app.demo.next() + else: + g.cls() + print('starting demo') + demo = MyDemo(c, trace=False) + demo.bind('callout', callout) + demo.bind('title', title) + demo.start(script_string=script_string) +except Exception: + g.app.demo = None + raise + +if c.isChanged(): c.save() +import imp +from leo.core.leoQt import QtGui +import leo.plugins.demo as demo_module +imp.reload(demo_module) +# A short example. Change as needed. +script_string = '''\ +callout('Callout 1 centered') +title('This is title 1') +### +callout('Callout 2 (700, 200)', position=[700, 200]) +title('This is title 2') +demo.next() +''' + +class MyDemo (demo_module.Demo): + + def setup_script(self): + '''Delete all previously shown widgets.''' + self.delete_widgets() +def callout(text, **keys): + w = demo_module.Callout(text, **keys) + +def title(text, **keys): + w = demo_module.Title(text, **keys) + + +<< docstring >> +### From leoSettings.leo +# Created 2017/05/30 +@language python +@tabwidth -4 +__version__ = '0.0' +<< version history >> +<< imports >> +@others +''' +<|docstring|> +''' + +@ +Put notes about each version here. +<|Initial version notes|> +import leo.core.leoGlobals as g + +<|imports|> +def init (): + + ok = g.app.gui.guiName() in ('qt','qttabs') + if ok: + if 1: # Create the commander class *before* the frame is created. + g.registerHandler('before-create-leo-frame',onCreate) + else: # Create the commander class *after* the frame is created. + g.registerHandler('after-create-leo-frame',onCreate) + g.plugin_signon(__name__) + return ok + +def onCreate (tag, keys): + + c = keys.get('c') + if c: + thePluginController = pluginController(c) + +class <|Controller Class Name|>: + + @others +def __init__ (self,c): + + self.c = c + # Warning: hook handlers must use keywords.get('c'), NOT self.c. + <|ivars|> + +''' +The @auto importer for the {|{x=get_language()}|} language. + +Created {|{x=time.strftime("%Y/%m/%d")}|} by the `importer;;` abbreviation. +''' +import leo.plugins.importers.linescanner as linescanner +Importer = linescanner.Importer +@others +importer_dict = { + 'class': {|{x=cap_name}|}_Importer, + 'extensions': [<|comma-separated lists of extensions|>], + # Example: ['.c', '.cc', '.c++', '.cpp', '.cxx', '.h', '.h++'] +} +@language python +@tabwidth -4 + + + +class {|{x=cap_name}|}_Importer(Importer): + '''The importer for the {|{x=name}|} language.''' + + def __init__(self, importCommands): + '''{|{x=cap_name}|}_Importer.__init__''' + # Init the base class. + Importer.__init__(self, + importCommands, + language = '{|{x=name}|}', + state_class = {|{x=cap_name}|}_ScanState, + strict = <|True leading whitespace is significant. Otherwise False|>, + ) + + @others + +# These can be overridden in subclasses. + +### define an override if desired... + +if 0: # The base class + def clean_headline(self, s): + '''Return a cleaned up headline s.''' + return s.strip() + +# A more complex example, for the C language. + +# def clean_headline(self, s): + # '''Return a cleaned up headline s.''' + # import re + # type1 = r'(static|extern)*' + # type2 = r'(void|int|float|double|char)*' + # class_pattern = r'\s*(%s)\s*class\s+(\w+)' % (type1) + # pattern = r'\s*(%s)\s*(%s)\s*(\w+)' % (type1, type2) + # m = re.match(class_pattern, s) + # if m: + # prefix1 = '%s ' % (m.group(1)) if m.group(1) else '' + # return '%sclass %s' % (prefix1, m.group(2)) + # m = re.match(pattern, s) + # if m: + # prefix1 = '%s ' % (m.group(1)) if m.group(1) else '' + # prefix2 = '%s ' % (m.group(2)) if m.group(2) else '' + # h = m.group(3) or '<no c function name>' + # return '%s%s%s' % (prefix1, prefix2, h) + # else: + # return s + +def clean_nodes(self, parent): + ''' + Clean all nodes in parent's tree. + Subclasses override this as desired. + See perl_i.clean_nodes for an example. + ''' + pass + +class {|{x=cap_name}|}_ScanState: + '''A class representing the state of the {|{x=name}|} line-oriented scan.''' + + def __init__(self, d=None): + '''{|{x=cap_name}|}_ScanState.__init__''' + if d: + prev = d.get('prev') + self.context = prev.context + ### Adjust these by hand. + self.curlies = prev.curlies + else: + self.context = '' + ### Adjust these by hand. + self.curlies = 0 + + def __repr__(self): + '''{|{x=cap_name}|}_ScanState.__repr__''' + ### Adjust these by hand. + return "{|{x=cap_name}|}_ScanState context: %r curlies: %s" % ( + self.context, self.curlies) + + __str__ = __repr__ + + @others + + +def level(self): + '''{|{x=cap_name}|}_ScanState.level.''' + return <|self.curlies|> + ### Examples: + # self.indent # for python, coffeescript. + # self.curlies + # (self, curlies, self.parens) + +@language rest +@wrap + +The @settings tree contains all active settings. + +Settings outside this tree have no effect. +def update(self, data): + ''' + {|{x=cap_name}|}_ScanState.update + + Update the state using the 6-tuple returned by v2_scan_line. + Return i = data[1] + ''' + context, i, delta_c, delta_p, delta_s, bs_nl = data + # All ScanState classes must have a context ivar. + self.context = context + self.curlies += delta_c + ### Update {|{x=cap_name}|}_ScanState ivars + # self.bs_nl = bs_nl + # self.parens += delta_p + # self.squares += delta_s + return i + + + +True: same as recent_files_group, except that even files (basenames) which are unique +have their containing path listed in the submenu - so visual clutter is reduced +but you can still see where things come from before you load them. + +False: don't use submenus for multiple path entries, unless recent_files_group +is true (and recent_files_omit_directories is False) + + +True: show user tips on startup. + + + + +def check(codeString, filename, reporter=None): + """ + Check the Python source given by C{codeString} for flakes. + + @param codeString: The Python source to check. + @type codeString: C{str} + + @param filename: The name of the file the source came from, used to report + errors. + @type filename: C{str} + + @param reporter: A L{Reporter} instance, where errors and warnings will be + reported. + + @return: The number of warnings emitted. + @rtype: C{int} + """ + if reporter is None: + reporter = modReporter._makeDefaultReporter() + # First, compile into an AST and handle syntax errors. + try: + tree = ast.parse(codeString, filename=filename) + except SyntaxError as e: + reporter.syntaxError(filename, e.args[0], e.lineno, e.offset, e.text) + return 1 + except Exception: + reporter.unexpectedError(filename, 'problem decoding source') + return 1 + # Okay, it's syntactically valid. Now check it. + w = checker.Checker(tree, filename=filename) + w.messages.sort(key=lambda m: m.lineno) + for warning in w.messages: + reporter.flake(warning) + return len(w.messages) + + + +def __init__(self, tree, filename='(none)', builtins=None, + withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): + self._nodeHandlers = {} + self._deferred = collections.deque() + self.deadScopes = [] + self.messages = [] + self.filename = filename + if builtins: + self.builtIns = self.builtIns.union(builtins) + self.withDoctest = withDoctest + self.exceptHandlers = [()] + self.root = tree + + self.scopeStack = [] + try: + scope_tp = Checker._ast_node_scope[type(tree)] + except KeyError: + raise RuntimeError('No scope implemented for the node %r' % tree) + + with self.in_scope(scope_tp): + for builtin in self.builtIns: + self.addBinding(None, Builtin(builtin)) + self.handleChildren(tree) + self._run_deferred() + + self.checkDeadScopes() + + if file_tokens: + warnings.warn( + '`file_tokens` will be removed in a future version', + stacklevel=2, + ) + + +def deferFunction(self, callable): + """ + Schedule a function handler to be called just before completion. + + This is used for handling function bodies, which must be deferred + because code later in the file might modify the global scope. When + `callable` is called, the scope at the time this is called will be + restored, however it will contain any new bindings added to it. + """ + self._deferred.append((callable, self.scopeStack[:], self.offset)) + + +def _run_deferred(self): + orig = (self.scopeStack, self.offset) + + while self._deferred: + handler, scope, offset = self._deferred.popleft() + self.scopeStack, self.offset = scope, offset + handler() + + self.scopeStack, self.offset = orig + + +def _in_doctest(self): + return (len(self.scopeStack) >= 2 and + isinstance(self.scopeStack[1], DoctestScope)) + + +@property +def futuresAllowed(self): + if not all(isinstance(scope, ModuleScope) + for scope in self.scopeStack): + return False + + return self.scope._futures_allowed + +@futuresAllowed.setter +def futuresAllowed(self, value): + assert value is False + if isinstance(self.scope, ModuleScope): + self.scope._futures_allowed = False + + +@property +def annotationsFutureEnabled(self): + scope = self.scopeStack[0] + if not isinstance(scope, ModuleScope): + return False + return scope._annotations_future_enabled + +@annotationsFutureEnabled.setter +def annotationsFutureEnabled(self, value): + assert value is True + assert isinstance(self.scope, ModuleScope) + self.scope._annotations_future_enabled = True + + +@property +def scope(self): + return self.scopeStack[-1] + +@contextlib.contextmanager +def in_scope(self, cls): + self.scopeStack.append(cls()) + try: + yield + finally: + self.deadScopes.append(self.scopeStack.pop()) + + +def checkPath(filename, reporter=None): + """ + Check the given path, printing out any warnings detected. + + @param reporter: A L{Reporter} instance, where errors and warnings will be + reported. + + @return: the number of warnings printed + """ + if reporter is None: + reporter = modReporter._makeDefaultReporter() + try: + with open(filename, 'rb') as f: + codestr = f.read() + except OSError as e: + reporter.unexpectedError(filename, e.args[1]) + return 1 + return check(codestr, filename, reporter) + + + +def checkDeadScopes(self): + """ + Look at scopes which have been fully examined and report names in them + which were imported but unused. + """ + for scope in self.deadScopes: + # imports in classes are public members + if isinstance(scope, ClassScope): + continue + + if isinstance(scope, FunctionScope): + for name, binding in scope.unused_assignments(): + self.report(messages.UnusedVariable, binding.source, name) + for name, binding in scope.unused_annotations(): + self.report(messages.UnusedAnnotation, binding.source, name) + + all_binding = scope.get('__all__') + if all_binding and not isinstance(all_binding, ExportBinding): + all_binding = None + + if all_binding: + all_names = set(all_binding.names) + undefined = [ + name for name in all_binding.names + if name not in scope + ] + else: + all_names = undefined = [] + + if undefined: + if not scope.importStarred and \ + os.path.basename(self.filename) != '__init__.py': + # Look for possible mistakes in the export list + for name in undefined: + self.report(messages.UndefinedExport, + scope['__all__'].source, name) + + # mark all import '*' as used by the undefined in __all__ + if scope.importStarred: + from_list = [] + for binding in scope.values(): + if isinstance(binding, StarImportation): + binding.used = all_binding + from_list.append(binding.fullName) + # report * usage, with a list of possible sources + from_list = ', '.join(sorted(from_list)) + for name in undefined: + self.report(messages.ImportStarUsage, + scope['__all__'].source, name, from_list) + + # Look for imported names that aren't used. + for value in scope.values(): + if isinstance(value, Importation): + used = value.used or value.name in all_names + if not used: + messg = messages.UnusedImport + self.report(messg, value.source, str(value)) + for node in value.redefined: + if isinstance(self.getParent(node), FOR_TYPES): + messg = messages.ImportShadowedByLoopVar + elif used: + continue + else: + messg = messages.RedefinedWhileUnused + self.report(messg, node, value.name, value.source) + + +def report(self, messageClass, *args, **kwargs): + self.messages.append(messageClass(self.filename, *args, **kwargs)) + + +def getParent(self, node): + # Lookup the first parent which is not Tuple, List or Starred + while True: + node = node._pyflakes_parent + if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): + return node + + +def getCommonAncestor(self, lnode, rnode, stop): + if ( + stop in (lnode, rnode) or + not ( + hasattr(lnode, '_pyflakes_parent') and + hasattr(rnode, '_pyflakes_parent') + ) + ): + return None + if lnode is rnode: + return lnode + + if (lnode._pyflakes_depth > rnode._pyflakes_depth): + return self.getCommonAncestor(lnode._pyflakes_parent, rnode, stop) + if (lnode._pyflakes_depth < rnode._pyflakes_depth): + return self.getCommonAncestor(lnode, rnode._pyflakes_parent, stop) + return self.getCommonAncestor( + lnode._pyflakes_parent, + rnode._pyflakes_parent, + stop, + ) + + +def descendantOf(self, node, ancestors, stop): + for a in ancestors: + if self.getCommonAncestor(node, a, stop): + return True + return False + + +def _getAncestor(self, node, ancestor_type): + parent = node + while True: + if parent is self.root: + return None + parent = self.getParent(parent) + if isinstance(parent, ancestor_type): + return parent + + +def getScopeNode(self, node): + return self._getAncestor(node, tuple(Checker._ast_node_scope.keys())) + + +def differentForks(self, lnode, rnode): + """True, if lnode and rnode are located on different forks of IF/TRY""" + ancestor = self.getCommonAncestor(lnode, rnode, self.root) + parts = getAlternatives(ancestor) + if parts: + for items in parts: + if self.descendantOf(lnode, items, ancestor) ^ \ + self.descendantOf(rnode, items, ancestor): + return True + return False + + +def addBinding(self, node, value): + """ + Called when a binding is altered. + + - `node` is the statement responsible for the change + - `value` is the new value, a Binding instance + """ + # assert value.source in (node, node._pyflakes_parent): + for scope in self.scopeStack[::-1]: + if value.name in scope: + break + existing = scope.get(value.name) + + if (existing and not isinstance(existing, Builtin) and + not self.differentForks(node, existing.source)): + + parent_stmt = self.getParent(value.source) + if isinstance(existing, Importation) and isinstance(parent_stmt, FOR_TYPES): + self.report(messages.ImportShadowedByLoopVar, + node, value.name, existing.source) + + elif scope is self.scope: + if ( + (not existing.used and value.redefines(existing)) and + (value.name != '_' or isinstance(existing, Importation)) and + not is_typing_overload(existing, self.scopeStack) + ): + self.report(messages.RedefinedWhileUnused, + node, value.name, existing.source) + + elif isinstance(existing, Importation) and value.redefines(existing): + existing.redefined.append(node) + + if value.name in self.scope: + # then assume the rebound name is used as a global or within a loop + value.used = self.scope[value.name].used + + # don't treat annotations as assignments if there is an existing value + # in scope + if value.name not in self.scope or not isinstance(value, Annotation): + if isinstance(value, NamedExprAssignment): + # PEP 572: use scope in which outermost generator is defined + scope = next( + scope + for scope in reversed(self.scopeStack) + if not isinstance(scope, GeneratorScope) + ) + # it may be a re-assignment to an already existing name + scope.setdefault(value.name, value) + else: + self.scope[value.name] = value + + +def _unknown_handler(self, node): + # this environment variable configures whether to error on unknown + # ast types. + # + # this is silent by default but the error is enabled for the pyflakes + # testsuite. + # + # this allows new syntax to be added to python without *requiring* + # changes from the pyflakes side. but will still produce an error + # in the pyflakes testsuite (so more specific handling can be added if + # needed). + if os.environ.get('PYFLAKES_ERROR_UNKNOWN'): + raise NotImplementedError(f'Unexpected type: {type(node)}') + else: + self.handleChildren(node) + + +def isPythonFile(filename): + """Return True if filename points to a Python file.""" + if filename.endswith('.py'): + return True + + # Avoid obvious Emacs backup files + if filename.endswith("~"): + return False + + max_bytes = 128 + + try: + with open(filename, 'rb') as f: + text = f.read(max_bytes) + if not text: + return False + except OSError: + return False + + return PYTHON_SHEBANG_REGEX.match(text) + + + +def getNodeHandler(self, node_class): + try: + return self._nodeHandlers[node_class] + except KeyError: + nodeType = node_class.__name__.upper() + self._nodeHandlers[node_class] = handler = getattr( + self, nodeType, self._unknown_handler, + ) + return handler + + +def handleNodeLoad(self, node, parent): + name = getNodeName(node) + if not name: + return + + # only the following can access class scoped variables (since classes + # aren't really a scope) + # - direct accesses (not within a nested scope) + # - generators + # - type annotations (for generics, etc.) + can_access_class_vars = None + importStarred = None + + # try enclosing function scopes and global scope + for scope in self.scopeStack[-1::-1]: + if isinstance(scope, ClassScope): + if name == '__class__': + return + elif can_access_class_vars is False: + # only generators used in a class scope can access the + # names of the class. this is skipped during the first + # iteration + continue + + binding = scope.get(name, None) + if isinstance(binding, Annotation) and not self._in_postponed_annotation: + scope[name].used = (self.scope, node) + continue + + if name == 'print' and isinstance(binding, Builtin): + if (isinstance(parent, ast.BinOp) and + isinstance(parent.op, ast.RShift)): + self.report(messages.InvalidPrintSyntax, node) + + try: + scope[name].used = (self.scope, node) + + # if the name of SubImportation is same as + # alias of other Importation and the alias + # is used, SubImportation also should be marked as used. + n = scope[name] + if isinstance(n, Importation) and n._has_alias(): + try: + scope[n.fullName].used = (self.scope, node) + except KeyError: + pass + except KeyError: + pass + else: + return + + importStarred = importStarred or scope.importStarred + + if can_access_class_vars is not False: + can_access_class_vars = isinstance( + scope, (TypeScope, GeneratorScope), + ) + + if importStarred: + from_list = [] + + for scope in self.scopeStack[-1::-1]: + for binding in scope.values(): + if isinstance(binding, StarImportation): + # mark '*' imports as used for each scope + binding.used = (self.scope, node) + from_list.append(binding.fullName) + + # report * usage, with a list of possible sources + from_list = ', '.join(sorted(from_list)) + self.report(messages.ImportStarUsage, node, name, from_list) + return + + if name == '__path__' and os.path.basename(self.filename) == '__init__.py': + # the special name __path__ is valid only in packages + return + + if name in DetectClassScopedMagic.names and isinstance(self.scope, ClassScope): + return + + # protected with a NameError handler? + if 'NameError' not in self.exceptHandlers[-1]: + self.report(messages.UndefinedName, node, name) + + +def handleNodeStore(self, node): + name = getNodeName(node) + if not name: + return + # if the name hasn't already been defined in the current scope + if isinstance(self.scope, FunctionScope) and name not in self.scope: + # for each function or module scope above us + for scope in self.scopeStack[:-1]: + if not isinstance(scope, (FunctionScope, ModuleScope)): + continue + # if the name was defined in that scope, and the name has + # been accessed already in the current scope, and hasn't + # been declared global + used = name in scope and scope[name].used + if used and used[0] is self.scope and name not in self.scope.globals: + # then it's probably a mistake + self.report(messages.UndefinedLocal, + scope[name].used[1], name, scope[name].source) + break + + parent_stmt = self.getParent(node) + if isinstance(parent_stmt, ast.AnnAssign) and parent_stmt.value is None: + binding = Annotation(name, node) + elif isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( + parent_stmt != node._pyflakes_parent and + not self.isLiteralTupleUnpacking(parent_stmt)): + binding = Binding(name, node) + elif ( + name == '__all__' and + isinstance(self.scope, ModuleScope) and + isinstance( + node._pyflakes_parent, + (ast.Assign, ast.AugAssign, ast.AnnAssign) + ) + ): + binding = ExportBinding(name, node._pyflakes_parent, self.scope) + elif isinstance(parent_stmt, ast.NamedExpr): + binding = NamedExprAssignment(name, node) + else: + binding = Assignment(name, node) + self.addBinding(node, binding) + + +def handleNodeDelete(self, node): + + def on_conditional_branch(): + """ + Return `True` if node is part of a conditional body. + """ + current = getattr(node, '_pyflakes_parent', None) + while current: + if isinstance(current, (ast.If, ast.While, ast.IfExp)): + return True + current = getattr(current, '_pyflakes_parent', None) + return False + + name = getNodeName(node) + if not name: + return + + if on_conditional_branch(): + # We cannot predict if this conditional branch is going to + # be executed. + return + + if isinstance(self.scope, FunctionScope) and name in self.scope.globals: + self.scope.globals.remove(name) + else: + try: + del self.scope[name] + except KeyError: + self.report(messages.UndefinedName, node, name) + + +@contextlib.contextmanager +def _enter_annotation(self, ann_type=AnnotationState.BARE): + orig, self._in_annotation = self._in_annotation, ann_type + try: + yield + finally: + self._in_annotation = orig + + +@property +def _in_postponed_annotation(self): + return ( + self._in_annotation == AnnotationState.STRING or + self.annotationsFutureEnabled + ) + + +def handleChildren(self, tree, omit=None): + for node in iter_child_nodes(tree, omit=omit): + self.handleNode(node, tree) + + +def isLiteralTupleUnpacking(self, node): + if isinstance(node, ast.Assign): + for child in node.targets + [node.value]: + if not hasattr(child, 'elts'): + return False + return True + + +def isDocstring(self, node): + """ + Determine if the given node is a docstring, as long as it is at the + correct place in the node tree. + """ + return ( + isinstance(node, ast.Expr) and + isinstance(node.value, ast.Constant) and + isinstance(node.value.value, str) + ) + + +def getDocstring(self, node): + if ( + isinstance(node, ast.Expr) and + isinstance(node.value, ast.Constant) and + isinstance(node.value.value, str) + ): + return node.value.value, node.lineno - 1 + else: + return None, None + + +def iterSourceCode(paths): + """ + Iterate over all Python source files in C{paths}. + + @param paths: A list of paths. Directories will be recursed into and + any .py files found will be yielded. Any non-directories will be + yielded as-is. + """ + for path in paths: + if os.path.isdir(path): + for dirpath, dirnames, filenames in os.walk(path): + for filename in filenames: + full_path = os.path.join(dirpath, filename) + if isPythonFile(full_path): + yield full_path + else: + yield path + + + +def handleNode(self, node, parent): + if node is None: + return + if self.offset and getattr(node, 'lineno', None) is not None: + node.lineno += self.offset[0] + node.col_offset += self.offset[1] + if ( + self.futuresAllowed and + self.nodeDepth == 0 and + not isinstance(node, ast.ImportFrom) and + not self.isDocstring(node) + ): + self.futuresAllowed = False + self.nodeDepth += 1 + node._pyflakes_depth = self.nodeDepth + node._pyflakes_parent = parent + try: + handler = self.getNodeHandler(node.__class__) + handler(node) + finally: + self.nodeDepth -= 1 + + +_getDoctestExamples = doctest.DocTestParser().get_examples + +def handleDoctests(self, node): + try: + (docstring, node_lineno) = self.getDocstring(node.body[0]) + examples = docstring and self._getDoctestExamples(docstring) + except (ValueError, IndexError): + # e.g. line 6 of the docstring for <string> has inconsistent + # leading whitespace: ... + return + if not examples: + return + + # Place doctest in module scope + saved_stack = self.scopeStack + self.scopeStack = [self.scopeStack[0]] + node_offset = self.offset or (0, 0) + with self.in_scope(DoctestScope): + if '_' not in self.scopeStack[0]: + self.addBinding(None, Builtin('_')) + for example in examples: + try: + tree = ast.parse(example.source, "<doctest>") + except SyntaxError as e: + position = (node_lineno + example.lineno + e.lineno, + example.indent + 4 + (e.offset or 0)) + self.report(messages.DoctestSyntaxError, node, position) + else: + self.offset = (node_offset[0] + node_lineno + example.lineno, + node_offset[1] + example.indent + 4) + self.handleChildren(tree) + self.offset = node_offset + self.scopeStack = saved_stack + + +@in_string_annotation +def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err): + try: + tree = ast.parse(s) + except SyntaxError: + self.report(err, node, s) + return + + body = tree.body + if len(body) != 1 or not isinstance(body[0], ast.Expr): + self.report(err, node, s) + return + + parsed_annotation = tree.body[0].value + for descendant in ast.walk(parsed_annotation): + if ( + 'lineno' in descendant._attributes and + 'col_offset' in descendant._attributes + ): + descendant.lineno = ref_lineno + descendant.col_offset = ref_col_offset + + self.handleNode(parsed_annotation, node) + + +def handle_annotation_always_deferred(self, annotation, parent): + fn = in_annotation(Checker.handleNode) + self.deferFunction(lambda: fn(self, annotation, parent)) + + +@in_annotation +def handleAnnotation(self, annotation, node): + if ( + isinstance(annotation, ast.Constant) and + isinstance(annotation.value, str) + ): + # Defer handling forward annotation. + self.deferFunction(functools.partial( + self.handleStringAnnotation, + annotation.value, + node, + annotation.lineno, + annotation.col_offset, + messages.ForwardAnnotationSyntaxError, + )) + elif self.annotationsFutureEnabled: + self.handle_annotation_always_deferred(annotation, node) + else: + self.handleNode(annotation, node) + + +def ignore(self, node): + pass + + +# "stmt" type nodes +DELETE = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = ASYNCWITH = \ + EXPR = ASSIGN = handleChildren + +PASS = ignore + +# "expr" type nodes +BOOLOP = UNARYOP = SET = ATTRIBUTE = STARRED = NAMECONSTANT = \ + NAMEDEXPR = handleChildren + +def SUBSCRIPT(self, node): + if _is_name_or_attr(node.value, 'Literal'): + with self._enter_annotation(AnnotationState.NONE): + self.handleChildren(node) + elif _is_name_or_attr(node.value, 'Annotated'): + self.handleNode(node.value, node) + + # py39+ + if isinstance(node.slice, ast.Tuple): + slice_tuple = node.slice + # <py39 + elif ( + isinstance(node.slice, ast.Index) and + isinstance(node.slice.value, ast.Tuple) + ): + slice_tuple = node.slice.value + else: + slice_tuple = None + + # not a multi-arg `Annotated` + if slice_tuple is None or len(slice_tuple.elts) < 2: + self.handleNode(node.slice, node) + else: + # the first argument is the type + self.handleNode(slice_tuple.elts[0], node) + # the rest of the arguments are not + with self._enter_annotation(AnnotationState.NONE): + for arg in slice_tuple.elts[1:]: + self.handleNode(arg, node) + + self.handleNode(node.ctx, node) + else: + if _is_any_typing_member(node.value, self.scopeStack): + with self._enter_annotation(): + self.handleChildren(node) + else: + self.handleChildren(node) + + +def _handle_string_dot_format(self, node): + try: + placeholders = tuple(parse_format_string(node.func.value.value)) + except ValueError as e: + self.report(messages.StringDotFormatInvalidFormat, node, e) + return + + auto = None + next_auto = 0 + + placeholder_positional = set() + placeholder_named = set() + + def _add_key(fmtkey): + """Returns True if there is an error which should early-exit""" + nonlocal auto, next_auto + + if fmtkey is None: # end of string or `{` / `}` escapes + return False + + # attributes / indices are allowed in `.format(...)` + fmtkey, _, _ = fmtkey.partition('.') + fmtkey, _, _ = fmtkey.partition('[') + + try: + fmtkey = int(fmtkey) + except ValueError: + pass + else: # fmtkey was an integer + if auto is True: + self.report(messages.StringDotFormatMixingAutomatic, node) + return True + else: + auto = False + + if fmtkey == '': + if auto is False: + self.report(messages.StringDotFormatMixingAutomatic, node) + return True + else: + auto = True + + fmtkey = next_auto + next_auto += 1 + + if isinstance(fmtkey, int): + placeholder_positional.add(fmtkey) + else: + placeholder_named.add(fmtkey) + + return False + + for _, fmtkey, spec, _ in placeholders: + if _add_key(fmtkey): + return + + # spec can also contain format specifiers + if spec is not None: + try: + spec_placeholders = tuple(parse_format_string(spec)) + except ValueError as e: + self.report(messages.StringDotFormatInvalidFormat, node, e) + return + + for _, spec_fmtkey, spec_spec, _ in spec_placeholders: + # can't recurse again + if spec_spec is not None and '{' in spec_spec: + self.report( + messages.StringDotFormatInvalidFormat, + node, + 'Max string recursion exceeded', + ) + return + if _add_key(spec_fmtkey): + return + + # bail early if there is *args or **kwargs + if ( + # *args + any(isinstance(arg, ast.Starred) for arg in node.args) or + # **kwargs + any(kwd.arg is None for kwd in node.keywords) + ): + return + + substitution_positional = set(range(len(node.args))) + substitution_named = {kwd.arg for kwd in node.keywords} + + extra_positional = substitution_positional - placeholder_positional + extra_named = substitution_named - placeholder_named + + missing_arguments = ( + (placeholder_positional | placeholder_named) - + (substitution_positional | substitution_named) + ) + + if extra_positional: + self.report( + messages.StringDotFormatExtraPositionalArguments, + node, + ', '.join(sorted(str(x) for x in extra_positional)), + ) + if extra_named: + self.report( + messages.StringDotFormatExtraNamedArguments, + node, + ', '.join(sorted(extra_named)), + ) + if missing_arguments: + self.report( + messages.StringDotFormatMissingArgument, + node, + ', '.join(sorted(str(x) for x in missing_arguments)), + ) + + +def CALL(self, node): + if ( + isinstance(node.func, ast.Attribute) and + isinstance(node.func.value, ast.Constant) and + isinstance(node.func.value.value, str) and + node.func.attr == 'format' + ): + self._handle_string_dot_format(node) + + omit = [] + annotated = [] + not_annotated = [] + + if ( + _is_typing(node.func, 'cast', self.scopeStack) and + len(node.args) >= 1 + ): + with self._enter_annotation(): + self.handleNode(node.args[0], node) + + elif _is_typing(node.func, 'TypeVar', self.scopeStack): + + # TypeVar("T", "int", "str") + omit += ["args"] + annotated += [arg for arg in node.args[1:]] + + # TypeVar("T", bound="str") + omit += ["keywords"] + annotated += [k.value for k in node.keywords if k.arg == "bound"] + not_annotated += [ + (k, ["value"] if k.arg == "bound" else None) + for k in node.keywords + ] + + elif _is_typing(node.func, "TypedDict", self.scopeStack): + # TypedDict("a", {"a": int}) + if len(node.args) > 1 and isinstance(node.args[1], ast.Dict): + omit += ["args"] + annotated += node.args[1].values + not_annotated += [ + (arg, ["values"] if i == 1 else None) + for i, arg in enumerate(node.args) + ] + + # TypedDict("a", a=int) + omit += ["keywords"] + annotated += [k.value for k in node.keywords] + not_annotated += [(k, ["value"]) for k in node.keywords] + + elif _is_typing(node.func, "NamedTuple", self.scopeStack): + # NamedTuple("a", [("a", int)]) + if ( + len(node.args) > 1 and + isinstance(node.args[1], (ast.Tuple, ast.List)) and + all(isinstance(x, (ast.Tuple, ast.List)) and + len(x.elts) == 2 for x in node.args[1].elts) + ): + omit += ["args"] + annotated += [elt.elts[1] for elt in node.args[1].elts] + not_annotated += [(elt.elts[0], None) for elt in node.args[1].elts] + not_annotated += [ + (arg, ["elts"] if i == 1 else None) + for i, arg in enumerate(node.args) + ] + not_annotated += [(elt, "elts") for elt in node.args[1].elts] + + # NamedTuple("a", a=int) + omit += ["keywords"] + annotated += [k.value for k in node.keywords] + not_annotated += [(k, ["value"]) for k in node.keywords] + + if omit: + with self._enter_annotation(AnnotationState.NONE): + for na_node, na_omit in not_annotated: + self.handleChildren(na_node, omit=na_omit) + self.handleChildren(node, omit=omit) + + with self._enter_annotation(): + for annotated_node in annotated: + self.handleNode(annotated_node, node) + else: + self.handleChildren(node) + + +def _handle_percent_format(self, node): + try: + placeholders = parse_percent_format(node.left.value) + except ValueError: + self.report( + messages.PercentFormatInvalidFormat, + node, + 'incomplete format', + ) + return + + named = set() + positional_count = 0 + positional = None + for _, placeholder in placeholders: + if placeholder is None: + continue + name, _, width, precision, conversion = placeholder + + if conversion == '%': + continue + + if conversion not in VALID_CONVERSIONS: + self.report( + messages.PercentFormatUnsupportedFormatCharacter, + node, + conversion, + ) + + if positional is None and conversion: + positional = name is None + + for part in (width, precision): + if part is not None and '*' in part: + if not positional: + self.report( + messages.PercentFormatStarRequiresSequence, + node, + ) + else: + positional_count += 1 + + if positional and name is not None: + self.report( + messages.PercentFormatMixedPositionalAndNamed, + node, + ) + return + elif not positional and name is None: + self.report( + messages.PercentFormatMixedPositionalAndNamed, + node, + ) + return + + if positional: + positional_count += 1 + else: + named.add(name) + + if ( + isinstance(node.right, (ast.List, ast.Tuple)) and + # does not have any *splats (py35+ feature) + not any( + isinstance(elt, ast.Starred) + for elt in node.right.elts + ) + ): + substitution_count = len(node.right.elts) + if positional and positional_count != substitution_count: + self.report( + messages.PercentFormatPositionalCountMismatch, + node, + positional_count, + substitution_count, + ) + elif not positional: + self.report(messages.PercentFormatExpectedMapping, node) + + if ( + isinstance(node.right, ast.Dict) and + all( + isinstance(k, ast.Constant) and isinstance(k.value, str) + for k in node.right.keys + ) + ): + if positional and positional_count > 1: + self.report(messages.PercentFormatExpectedSequence, node) + return + + substitution_keys = {k.value for k in node.right.keys} + extra_keys = substitution_keys - named + missing_keys = named - substitution_keys + if not positional and extra_keys: + self.report( + messages.PercentFormatExtraNamedArguments, + node, + ', '.join(sorted(extra_keys)), + ) + if not positional and missing_keys: + self.report( + messages.PercentFormatMissingArgument, + node, + ', '.join(sorted(missing_keys)), + ) + + +def checkRecursive(paths, reporter): + """ + Recursively check all source files in C{paths}. + + @param paths: A list of paths to Python source files and directories + containing Python source files. + @param reporter: A L{Reporter} where all of the warnings and errors + will be reported to. + @return: The number of warnings found. + """ + warnings = 0 + for sourcePath in iterSourceCode(paths): + warnings += checkPath(sourcePath, reporter) + return warnings + + + +def BINOP(self, node): + if ( + isinstance(node.op, ast.Mod) and + isinstance(node.left, ast.Constant) and + isinstance(node.left.value, str) + ): + self._handle_percent_format(node) + self.handleChildren(node) + + +def CONSTANT(self, node): + if isinstance(node.value, str) and self._in_annotation: + fn = functools.partial( + self.handleStringAnnotation, + node.value, + node, + node.lineno, + node.col_offset, + messages.ForwardAnnotationSyntaxError, + ) + self.deferFunction(fn) + +# "slice" type nodes +SLICE = EXTSLICE = INDEX = handleChildren + +# expression contexts are node instances too, though being constants +LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore + +# same for operators +AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ + BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ + EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = \ + MATMULT = ignore + + +def RAISE(self, node): + self.handleChildren(node) + + arg = node.exc + + if isinstance(arg, ast.Call): + if is_notimplemented_name_node(arg.func): + # Handle "raise NotImplemented(...)" + self.report(messages.RaiseNotImplemented, node) + elif is_notimplemented_name_node(arg): + # Handle "raise NotImplemented" + self.report(messages.RaiseNotImplemented, node) + + +_in_fstring = False + +def JOINEDSTR(self, node): + if ( + # the conversion / etc. flags are parsed as f-strings without + # placeholders + not self._in_fstring and + not any(isinstance(x, ast.FormattedValue) for x in node.values) + ): + self.report(messages.FStringMissingPlaceholders, node) + + self._in_fstring, orig = True, self._in_fstring + try: + self.handleChildren(node) + finally: + self._in_fstring = orig + + +def DICT(self, node): + # Complain if there are duplicate keys with different values + # If they have the same value it's not going to cause potentially + # unexpected behaviour so we'll not complain. + keys = [ + convert_to_value(key) for key in node.keys + ] + + key_counts = collections.Counter(keys) + duplicate_keys = [ + key for key, count in key_counts.items() + if count > 1 + ] + + for key in duplicate_keys: + key_indices = [i for i, i_key in enumerate(keys) if i_key == key] + + values = collections.Counter( + convert_to_value(node.values[index]) + for index in key_indices + ) + if any(count == 1 for value, count in values.items()): + for key_index in key_indices: + key_node = node.keys[key_index] + if isinstance(key, VariableKey): + self.report(messages.MultiValueRepeatedKeyVariable, + key_node, + key.name) + else: + self.report( + messages.MultiValueRepeatedKeyLiteral, + key_node, + key, + ) + self.handleChildren(node) + + +def IF(self, node): + if isinstance(node.test, ast.Tuple) and node.test.elts != []: + self.report(messages.IfTuple, node) + self.handleChildren(node) + +IFEXP = IF + + +def ASSERT(self, node): + if isinstance(node.test, ast.Tuple) and node.test.elts != []: + self.report(messages.AssertTuple, node) + self.handleChildren(node) + + +def GLOBAL(self, node): + """ + Keep track of globals declarations. + """ + global_scope_index = 1 if self._in_doctest() else 0 + global_scope = self.scopeStack[global_scope_index] + + # Ignore 'global' statement in global scope. + if self.scope is not global_scope: + + # One 'global' statement can bind multiple (comma-delimited) names. + for node_name in node.names: + node_value = Assignment(node_name, node) + + # Remove UndefinedName messages already reported for this name. + # TODO: if the global is not used in this scope, it does not + # become a globally defined name. See test_unused_global. + self.messages = [ + m for m in self.messages if not + isinstance(m, messages.UndefinedName) or + m.message_args[0] != node_name] + + # Bind name to global scope if it doesn't exist already. + global_scope.setdefault(node_name, node_value) + + # Bind name to non-global scopes, but as already "used". + node_value.used = (global_scope, node) + for scope in self.scopeStack[global_scope_index + 1:]: + scope[node_name] = node_value + +NONLOCAL = GLOBAL + + +def GENERATOREXP(self, node): + with self.in_scope(GeneratorScope): + self.handleChildren(node) + +LISTCOMP = DICTCOMP = SETCOMP = GENERATOREXP + + +def NAME(self, node): + """ + Handle occurrence of Name (which can be a load/store/delete access.) + """ + # Locate the name in locals / function / globals scopes. + if isinstance(node.ctx, ast.Load): + self.handleNodeLoad(node, self.getParent(node)) + if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and + isinstance(node._pyflakes_parent, ast.Call)): + # we are doing locals() call in current scope + self.scope.usesLocals = True + elif isinstance(node.ctx, ast.Store): + self.handleNodeStore(node) + elif isinstance(node.ctx, ast.Del): + self.handleNodeDelete(node) + else: + # Unknown context + raise RuntimeError(f"Got impossible expression context: {node.ctx!r}") + + +def _exitOnSignal(sigName, message): + """Handles a signal with sys.exit. + + Some of these signals (SIGPIPE, for example) don't exist or are invalid on + Windows. So, ignore errors that might arise. + """ + import signal + + try: + sigNumber = getattr(signal, sigName) + except AttributeError: + # the signal constants defined in the signal module are defined by + # whether the C library supports them or not. So, SIGPIPE might not + # even be defined. + return + + def handler(sig, f): + sys.exit(message) + + try: + signal.signal(sigNumber, handler) + except ValueError: + # It's also possible the signal is defined, but then it's invalid. In + # this case, signal.signal raises ValueError. + pass + + + +def CONTINUE(self, node): + # Walk the tree up until we see a loop (OK), a function or class + # definition (not OK), for 'continue', a finally block (not OK), or + # the top module scope (not OK) + n = node + while hasattr(n, '_pyflakes_parent'): + n, n_child = n._pyflakes_parent, n + if isinstance(n, (ast.While, ast.For, ast.AsyncFor)): + # Doesn't apply unless it's in the loop itself + if n_child not in n.orelse: + return + if isinstance(n, (ast.FunctionDef, ast.ClassDef)): + break + if isinstance(node, ast.Continue): + self.report(messages.ContinueOutsideLoop, node) + else: # ast.Break + self.report(messages.BreakOutsideLoop, node) + +BREAK = CONTINUE + + +def RETURN(self, node): + if isinstance(self.scope, (ClassScope, ModuleScope)): + self.report(messages.ReturnOutsideFunction, node) + return + + if ( + node.value and + hasattr(self.scope, 'returnValue') and + not self.scope.returnValue + ): + self.scope.returnValue = node.value + self.handleNode(node.value, node) + + +def YIELD(self, node): + if isinstance(self.scope, (ClassScope, ModuleScope)): + self.report(messages.YieldOutsideFunction, node) + return + + self.handleNode(node.value, node) + +AWAIT = YIELDFROM = YIELD + + +def FUNCTIONDEF(self, node): + for deco in node.decorator_list: + self.handleNode(deco, node) + + with self._type_param_scope(node): + self.LAMBDA(node) + + self.addBinding(node, FunctionDefinition(node.name, node)) + # doctest does not process doctest within a doctest, + # or in nested functions. + if (self.withDoctest and + not self._in_doctest() and + not isinstance(self.scope, FunctionScope)): + self.deferFunction(lambda: self.handleDoctests(node)) + + +ASYNCFUNCTIONDEF = FUNCTIONDEF + +def LAMBDA(self, node): + args = [] + annotations = [] + + for arg in node.args.posonlyargs: + args.append(arg.arg) + annotations.append(arg.annotation) + for arg in node.args.args + node.args.kwonlyargs: + args.append(arg.arg) + annotations.append(arg.annotation) + defaults = node.args.defaults + node.args.kw_defaults + + has_annotations = not isinstance(node, ast.Lambda) + + for arg_name in ('vararg', 'kwarg'): + wildcard = getattr(node.args, arg_name) + if not wildcard: + continue + args.append(wildcard.arg) + if has_annotations: + annotations.append(wildcard.annotation) + + if has_annotations: + annotations.append(node.returns) + + if len(set(args)) < len(args): + for (idx, arg) in enumerate(args): + if arg in args[:idx]: + self.report(messages.DuplicateArgument, node, arg) + + for annotation in annotations: + self.handleAnnotation(annotation, node) + + for default in defaults: + self.handleNode(default, node) + + def runFunction(): + with self.in_scope(FunctionScope): + self.handleChildren( + node, + omit=('decorator_list', 'returns', 'type_params'), + ) + + self.deferFunction(runFunction) + + +def ARGUMENTS(self, node): + self.handleChildren(node, omit=('defaults', 'kw_defaults')) + + +def ARG(self, node): + self.addBinding(node, Argument(node.arg, self.getScopeNode(node))) + + +def CLASSDEF(self, node): + """ + Check names used in a class definition, including its decorators, base + classes, and the body of its definition. Additionally, add its name to + the current scope. + """ + for deco in node.decorator_list: + self.handleNode(deco, node) + + with self._type_param_scope(node): + for baseNode in node.bases: + self.handleNode(baseNode, node) + for keywordNode in node.keywords: + self.handleNode(keywordNode, node) + with self.in_scope(ClassScope): + # doctest does not process doctest within a doctest + # classes within classes are processed. + if (self.withDoctest and + not self._in_doctest() and + not isinstance(self.scope, FunctionScope)): + self.deferFunction(lambda: self.handleDoctests(node)) + for stmt in node.body: + self.handleNode(stmt, node) + + self.addBinding(node, ClassDefinition(node.name, node)) + + +def AUGASSIGN(self, node): + self.handleNodeLoad(node.target, node) + self.handleNode(node.value, node) + self.handleNode(node.target, node) + + +def TUPLE(self, node): + if isinstance(node.ctx, ast.Store): + # Python 3 advanced tuple unpacking: a, *b, c = d. + # Only one starred expression is allowed, and no more than 1<<8 + # assignments are allowed before a stared expression. There is + # also a limit of 1<<24 expressions after the starred expression, + # which is impossible to test due to memory restrictions, but we + # add it here anyway + has_starred = False + star_loc = -1 + for i, n in enumerate(node.elts): + if isinstance(n, ast.Starred): + if has_starred: + self.report(messages.TwoStarredExpressions, node) + # The SyntaxError doesn't distinguish two from more + # than two. + break + has_starred = True + star_loc = i + if star_loc >= 1 << 8 or len(node.elts) - star_loc - 1 >= 1 << 24: + self.report(messages.TooManyExpressionsInStarredAssignment, node) + self.handleChildren(node) + +LIST = TUPLE + + +def _get_version(): + """ + Retrieve and format package version along with python version & OS used + """ + return ('%s Python %s on %s' % + (__version__, platform.python_version(), platform.system())) + + + +def IMPORT(self, node): + for alias in node.names: + if '.' in alias.name and not alias.asname: + importation = SubmoduleImportation(alias.name, node) + else: + name = alias.asname or alias.name + importation = Importation(name, node, alias.name) + self.addBinding(node, importation) + + +def IMPORTFROM(self, node): + if node.module == '__future__': + if not self.futuresAllowed: + self.report(messages.LateFutureImport, node) + else: + self.futuresAllowed = False + + module = ('.' * node.level) + (node.module or '') + + for alias in node.names: + name = alias.asname or alias.name + if node.module == '__future__': + importation = FutureImportation(name, node, self.scope) + if alias.name not in __future__.all_feature_names: + self.report(messages.FutureFeatureNotDefined, + node, alias.name) + if alias.name == 'annotations': + self.annotationsFutureEnabled = True + elif alias.name == '*': + if not isinstance(self.scope, ModuleScope): + self.report(messages.ImportStarNotPermitted, + node, module) + continue + + self.scope.importStarred = True + self.report(messages.ImportStarUsed, node, module) + importation = StarImportation(module, node) + else: + importation = ImportationFrom(name, node, + module, alias.name) + self.addBinding(node, importation) + + +def TRY(self, node): + handler_names = [] + # List the exception handlers + for i, handler in enumerate(node.handlers): + if isinstance(handler.type, ast.Tuple): + for exc_type in handler.type.elts: + handler_names.append(getNodeName(exc_type)) + elif handler.type: + handler_names.append(getNodeName(handler.type)) + + if handler.type is None and i < len(node.handlers) - 1: + self.report(messages.DefaultExceptNotLast, handler) + # Memorize the except handlers and process the body + self.exceptHandlers.append(handler_names) + for child in node.body: + self.handleNode(child, node) + self.exceptHandlers.pop() + # Process the other nodes: "except:", "else:", "finally:" + self.handleChildren(node, omit='body') + +TRYSTAR = TRY + + +def EXCEPTHANDLER(self, node): + if node.name is None: + self.handleChildren(node) + return + + # If the name already exists in the scope, modify state of existing + # binding. + if node.name in self.scope: + self.handleNodeStore(node) + + # 3.x: the name of the exception, which is not a Name node, but a + # simple string, creates a local that is only bound within the scope of + # the except: block. As such, temporarily remove the existing binding + # to more accurately determine if the name is used in the except: + # block. + + try: + prev_definition = self.scope.pop(node.name) + except KeyError: + prev_definition = None + + self.handleNodeStore(node) + self.handleChildren(node) + + # See discussion on https://github.com/PyCQA/pyflakes/pull/59 + + # We're removing the local name since it's being unbound after leaving + # the except: block and it's always unbound if the except: block is + # never entered. This will cause an "undefined name" error raised if + # the checked code tries to use the name afterwards. + # + # Unless it's been removed already. Then do nothing. + + try: + binding = self.scope.pop(node.name) + except KeyError: + pass + else: + if not binding.used: + self.report(messages.UnusedVariable, node, node.name) + + # Restore. + if prev_definition: + self.scope[node.name] = prev_definition + + +def ANNASSIGN(self, node): + self.handleAnnotation(node.annotation, node) + # If the assignment has value, handle the *value* now. + if node.value: + # If the annotation is `TypeAlias`, handle the *value* as an annotation. + if _is_typing(node.annotation, 'TypeAlias', self.scopeStack): + self.handleAnnotation(node.value, node) + else: + self.handleNode(node.value, node) + self.handleNode(node.target, node) + + +def COMPARE(self, node): + left = node.left + for op, right in zip(node.ops, node.comparators): + if ( + isinstance(op, (ast.Is, ast.IsNot)) and ( + _is_const_non_singleton(left) or + _is_const_non_singleton(right) + ) + ): + self.report(messages.IsLiteral, node) + left = right + + self.handleChildren(node) + + +MATCH = MATCH_CASE = MATCHCLASS = MATCHOR = MATCHSEQUENCE = handleChildren +MATCHSINGLETON = MATCHVALUE = handleChildren + +def _match_target(self, node): + self.handleNodeStore(node) + self.handleChildren(node) + +MATCHAS = MATCHMAPPING = MATCHSTAR = _match_target + + +@contextlib.contextmanager +def _type_param_scope(self, node): + with contextlib.ExitStack() as ctx: + if sys.version_info >= (3, 12): + ctx.enter_context(self.in_scope(TypeScope)) + for param in node.type_params: + self.handleNode(param, node) + yield + + +def TYPEVAR(self, node): + self.handleNodeStore(node) + self.handle_annotation_always_deferred(node.bound, node) + +PARAMSPEC = TYPEVARTUPLE = handleNodeStore + + +def TYPEALIAS(self, node): + self.handleNode(node.name, node) + with self._type_param_scope(node): + self.handle_annotation_always_deferred(node.value, node) + +def main(prog=None, args=None): + """Entry point for the script "pyflakes".""" + import argparse + + # Handle "Keyboard Interrupt" and "Broken pipe" gracefully + _exitOnSignal('SIGINT', '... stopped') + _exitOnSignal('SIGPIPE', 1) + + parser = argparse.ArgumentParser(prog=prog, + description='Check Python source files for errors') + parser.add_argument('-V', '--version', action='version', version=_get_version()) + parser.add_argument('path', nargs='*', + help='Path(s) of Python file(s) to check. STDIN if not given.') + args = parser.parse_args(args=args).path + reporter = modReporter._makeDefaultReporter() + if args: + warnings = checkRecursive(args, reporter) + else: + warnings = check(sys.stdin.read(), '<stdin>', reporter) + raise SystemExit(warnings > 0) + +@path pyflakes +""" +Provide the class Message and its subclasses. +""" + + +@others +@language python +@tabwidth -4 + +class Message: + message = '' + message_args = () + + def __init__(self, filename, loc): + self.filename = filename + self.lineno = loc.lineno + self.col = loc.col_offset + + def __str__(self): + return '{}:{}:{}: {}'.format(self.filename, self.lineno, self.col+1, + self.message % self.message_args) + + + +class UnusedImport(Message): + message = '%r imported but unused' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + + +class RedefinedWhileUnused(Message): + message = 'redefinition of unused %r from line %r' + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + + +class ImportShadowedByLoopVar(Message): + @others + +class ImportStarNotPermitted(Message): + @others + +class ImportStarUsed(Message): + @others + +class ImportStarUsage(Message): + @others + +class UndefinedName(Message): + @others + +class DoctestSyntaxError(Message): + @others + +@path pyflakes +""" +Main module. + +Implement the central Checker class. +Also, it models the Bindings and Scopes. +""" +import __future__ +import builtins +import ast +import collections +import contextlib +import doctest +import functools +import os +import re +import string +import sys +import warnings + +from pyflakes import messages + +<< checker.py: globals >> + + +@others +@language python +@tabwidth -4 + +class UndefinedExport(Message): + @others + +class UndefinedLocal(Message): + @others + +class DuplicateArgument(Message): + @others + +class MultiValueRepeatedKeyLiteral(Message): + @others + +class MultiValueRepeatedKeyVariable(Message): + @others + +class LateFutureImport(Message): + message = 'from __future__ imports must occur at the beginning of the file' + + + +class FutureFeatureNotDefined(Message): + """An undefined __future__ feature name was imported.""" + @others + +class UnusedVariable(Message): + """ + Indicates that a variable has been explicitly assigned to but not actually + used. + """ + @others + +class UnusedAnnotation(Message): + """ + Indicates that a variable has been explicitly annotated to but not actually + used. + """ + @others + +class ReturnOutsideFunction(Message): + """ + Indicates a return statement outside of a function/method. + """ + message = '\'return\' outside function' + + + +def getAlternatives(n): + if isinstance(n, ast.If): + return [n.body] + elif isinstance(n, ast.Try): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] + elif sys.version_info >= (3, 10) and isinstance(n, ast.Match): + return [mc.body for mc in n.cases] + + + +class YieldOutsideFunction(Message): + """ + Indicates a yield or yield from statement outside of a function/method. + """ + message = '\'yield\' outside function' + + + +# For whatever reason, Python gives different error messages for these two. We +# match the Python error message exactly. +class ContinueOutsideLoop(Message): + """ + Indicates a continue statement outside of a while or for loop. + """ + message = '\'continue\' not properly in loop' + + + +class BreakOutsideLoop(Message): + """ + Indicates a break statement outside of a while or for loop. + """ + message = '\'break\' outside loop' + + + +class DefaultExceptNotLast(Message): + """ + Indicates an except: block as not the last exception handler. + """ + message = 'default \'except:\' must be last' + + + +class TwoStarredExpressions(Message): + """ + Two or more starred expressions in an assignment (a, *b, *c = d). + """ + message = 'two starred expressions in assignment' + + + +class TooManyExpressionsInStarredAssignment(Message): + """ + Too many expressions in an assignment with star-unpacking + """ + message = 'too many expressions in star-unpacking assignment' + + + +class IfTuple(Message): + """ + Conditional test is a non-empty tuple literal, which are always True. + """ + message = '\'if tuple literal\' is always true, perhaps remove accidental comma?' + + + +class AssertTuple(Message): + """ + Assertion test is a non-empty tuple literal, which are always True. + """ + message = 'assertion is always true, perhaps remove parentheses?' + + + +class ForwardAnnotationSyntaxError(Message): + @others + +class RaiseNotImplemented(Message): + message = "'raise NotImplemented' should be 'raise NotImplementedError'" + + + +def _is_singleton(node): # type: (ast.AST) -> bool + return ( + isinstance(node, ast.Constant) and + isinstance(node.value, (bool, type(Ellipsis), type(None))) + ) + + + +class InvalidPrintSyntax(Message): + message = 'use of >> is invalid with print function' + + + +class IsLiteral(Message): + message = 'use ==/!= to compare constant literals (str, bytes, int, float, tuple)' + + + +class FStringMissingPlaceholders(Message): + message = 'f-string is missing placeholders' + + + +class StringDotFormatExtraPositionalArguments(Message): + @others + +class StringDotFormatExtraNamedArguments(Message): + @others + +class StringDotFormatMissingArgument(Message): + @others + +class StringDotFormatMixingAutomatic(Message): + message = "'...'.format(...) mixes automatic and manual numbering" + + + +class StringDotFormatInvalidFormat(Message): + @others + +class PercentFormatInvalidFormat(Message): + @others + +class PercentFormatMixedPositionalAndNamed(Message): + message = "'...' %% ... has mixed positional and named placeholders" + + + +def _is_tuple_constant(node): # type: (ast.AST) -> bool + return ( + isinstance(node, ast.Tuple) and + all(_is_constant(elt) for elt in node.elts) + ) + + + +class PercentFormatUnsupportedFormatCharacter(Message): + @others + +class PercentFormatPositionalCountMismatch(Message): + @others + +class PercentFormatExtraNamedArguments(Message): + @others + +class PercentFormatMissingArgument(Message): + @others + +class PercentFormatExpectedMapping(Message): + message = "'...' %% ... expected mapping but got sequence" + + + +class PercentFormatExpectedSequence(Message): + message = "'...' %% ... expected sequence but got mapping" + + + +class PercentFormatStarRequiresSequence(Message): + message = "'...' %% ... `*` specifier requires sequence" + +def _is_constant(node): + return isinstance(node, ast.Constant) or _is_tuple_constant(node) + + + +message = 'import %r from line %r shadowed by loop variable' + +def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + + +message = "'from %s import *' only allowed at module level" + +def __init__(self, filename, loc, modname): + Message.__init__(self, filename, loc) + self.message_args = (modname,) + + + +message = "'from %s import *' used; unable to detect undefined names" + +def __init__(self, filename, loc, modname): + Message.__init__(self, filename, loc) + self.message_args = (modname,) + + + +message = "%r may be undefined, or defined from star imports: %s" + +def __init__(self, filename, loc, name, from_list): + Message.__init__(self, filename, loc) + self.message_args = (name, from_list) + + + +message = 'undefined name %r' + +def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + + +message = 'syntax error in doctest' + +def __init__(self, filename, loc, position=None): + Message.__init__(self, filename, loc) + if position: + (self.lineno, self.col) = position + self.message_args = () + + + +message = 'undefined name %r in __all__' + +def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + + +message = 'local variable %r {0} referenced before assignment' + +default = 'defined in enclosing scope on line %r' +builtin = 'defined as a builtin' + +def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + if orig_loc is None: + self.message = self.message.format(self.builtin) + self.message_args = name + else: + self.message = self.message.format(self.default) + self.message_args = (name, orig_loc.lineno) + + + +message = 'duplicate argument %r in function definition' + +def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + + +def _is_const_non_singleton(node): # type: (ast.AST) -> bool + return _is_constant(node) and not _is_singleton(node) + + + +message = 'dictionary key %r repeated with different values' + +def __init__(self, filename, loc, key): + Message.__init__(self, filename, loc) + self.message_args = (key,) + + + +message = 'dictionary key variable %s repeated with different values' + +def __init__(self, filename, loc, key): + Message.__init__(self, filename, loc) + self.message_args = (key,) + + + +message = 'future feature %s is not defined' + +def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + + +message = 'local variable %r is assigned to but never used' + +def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) + self.message_args = (names,) + + + +message = 'local variable %r is annotated but never used' + +def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) + self.message_args = (names,) + + + +message = 'syntax error in forward annotation %r' + +def __init__(self, filename, loc, annotation): + Message.__init__(self, filename, loc) + self.message_args = (annotation,) + + + +message = "'...'.format(...) has unused arguments at position(s): %s" + +def __init__(self, filename, loc, extra_positions): + Message.__init__(self, filename, loc) + self.message_args = (extra_positions,) + + + +message = "'...'.format(...) has unused named argument(s): %s" + +def __init__(self, filename, loc, extra_keywords): + Message.__init__(self, filename, loc) + self.message_args = (extra_keywords,) + + + +message = "'...'.format(...) is missing argument(s) for placeholder(s): %s" + +def __init__(self, filename, loc, missing_arguments): + Message.__init__(self, filename, loc) + self.message_args = (missing_arguments,) + + + +message = "'...'.format(...) has invalid format string: %s" + +def __init__(self, filename, loc, error): + Message.__init__(self, filename, loc) + self.message_args = (error,) + + + +def _is_name_or_attr(node, name): # type: (ast.AST, str) -> bool + return ( + (isinstance(node, ast.Name) and node.id == name) or + (isinstance(node, ast.Attribute) and node.attr == name) + ) + + + +message = "'...' %% ... has invalid format string: %s" + +def __init__(self, filename, loc, error): + Message.__init__(self, filename, loc) + self.message_args = (error,) + + + +message = "'...' %% ... has unsupported format character %r" + +def __init__(self, filename, loc, c): + Message.__init__(self, filename, loc) + self.message_args = (c,) + + + +message = "'...' %% ... has %d placeholder(s) but %d substitution(s)" + +def __init__(self, filename, loc, n_placeholders, n_substitutions): + Message.__init__(self, filename, loc) + self.message_args = (n_placeholders, n_substitutions) + + + +message = "'...' %% ... has unused named argument(s): %s" + +def __init__(self, filename, loc, extra_keywords): + Message.__init__(self, filename, loc) + self.message_args = (extra_keywords,) + + + +message = "'...' %% ... is missing argument(s) for placeholder(s): %s" + +def __init__(self, filename, loc, missing_arguments): + Message.__init__(self, filename, loc) + self.message_args = (missing_arguments,) + + + +@path pyflakes +""" +Provide the Reporter class. +""" + +import re +import sys + + +@others +@language python +@tabwidth -4 + +class Reporter: + """ + Formats the results of pyflakes checks to users. + """ + + @others + +def _makeDefaultReporter(): + """ + Make a reporter that can be used when no reporter is specified. + """ + return Reporter(sys.stdout, sys.stderr) + +def __init__(self, warningStream, errorStream): + """ + Construct a L{Reporter}. + + @param warningStream: A file-like object where warnings will be + written to. The stream's C{write} method must accept unicode. + C{sys.stdout} is a good value. + @param errorStream: A file-like object where error output will be + written to. The stream's C{write} method must accept unicode. + C{sys.stderr} is a good value. + """ + self._stdout = warningStream + self._stderr = errorStream + + +def unexpectedError(self, filename, msg): + """ + An unexpected error occurred trying to process C{filename}. + + @param filename: The path to a file that we could not process. + @ptype filename: C{unicode} + @param msg: A message explaining the problem. + @ptype msg: C{unicode} + """ + self._stderr.write(f"{filename}: {msg}\n") + + +def _must_match(regex, string, pos): + match = regex.match(string, pos) + assert match is not None + return match + + + +def syntaxError(self, filename, msg, lineno, offset, text): + """ + There was a syntax error in C{filename}. + + @param filename: The path to the file with the syntax error. + @ptype filename: C{unicode} + @param msg: An explanation of the syntax error. + @ptype msg: C{unicode} + @param lineno: The line number where the syntax error occurred. + @ptype lineno: C{int} + @param offset: The column on which the syntax error occurred, or None. + @ptype offset: C{int} + @param text: The source code containing the syntax error. + @ptype text: C{unicode} + """ + if text is None: + line = None + else: + line = text.splitlines()[-1] + + # lineno might be None if the error was during tokenization + # lineno might be 0 if the error came from stdin + lineno = max(lineno or 0, 1) + + if offset is not None: + # some versions of python emit an offset of -1 for certain encoding errors + offset = max(offset, 1) + self._stderr.write('%s:%d:%d: %s\n' % + (filename, lineno, offset, msg)) + else: + self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg)) + + if line is not None: + self._stderr.write(line) + self._stderr.write('\n') + if offset is not None: + self._stderr.write(re.sub(r'\S', ' ', line[:offset - 1]) + + "^\n") + + +def flake(self, message): + """ + pyflakes found something wrong with the code. + + @param: A L{pyflakes.messages.Message}. + """ + self._stdout.write(str(message)) + self._stdout.write('\n') + + + + +# Empty file. +@path pyflakes/test +import ast +import textwrap +import unittest + +from pyflakes import checker + +__all__ = ['TestCase', 'skip', 'skipIf'] + +skip = unittest.skip +skipIf = unittest.skipIf + + +@others +{}'''.format(input, expectedOutputs, '\n'.join([str(o) for o in w.messages]))) + return w +@language python +@tabwidth -4 + +class TestCase(unittest.TestCase): + +@others + + withDoctest = False + + def flakes(self, input, *expectedOutputs, **kw): + tree = ast.parse(textwrap.dedent(input)) + if kw.get('is_segment'): + tree = tree.body[0] + kw.pop('is_segment') + w = checker.Checker(tree, withDoctest=self.withDoctest, **kw) + outputs = [type(o) for o in w.messages] + expectedOutputs = list(expectedOutputs) + outputs.sort(key=lambda t: t.__name__) + expectedOutputs.sort(key=lambda t: t.__name__) + self.assertEqual(outputs, expectedOutputs, '''\ +for input: +{} +expected outputs: +{!r} +but got: + +def parse_percent_format(s): + """Parses the string component of a `'...' % ...` format call + + Copied from https://github.com/asottile/pyupgrade at v1.20.1 + """ + + def _parse_inner(): + string_start = 0 + string_end = 0 + in_fmt = False + + i = 0 + while i < len(s): + if not in_fmt: + try: + i = s.index('%', i) + except ValueError: # no more % fields! + yield s[string_start:], None + return + else: + string_end = i + i += 1 + in_fmt = True + else: + key_match = MAPPING_KEY_RE.match(s, i) + if key_match: + key = key_match.group(1) + i = key_match.end() + else: + key = None + + conversion_flag_match = _must_match(CONVERSION_FLAG_RE, s, i) + conversion_flag = conversion_flag_match.group() or None + i = conversion_flag_match.end() + + width_match = _must_match(WIDTH_RE, s, i) + width = width_match.group() or None + i = width_match.end() + + precision_match = _must_match(PRECISION_RE, s, i) + precision = precision_match.group() or None + i = precision_match.end() + + # length modifier is ignored + i = _must_match(LENGTH_RE, s, i).end() + + try: + conversion = s[i] + except IndexError: + raise ValueError('end-of-string while parsing format') + i += 1 + + fmt = (key, conversion_flag, width, precision, conversion) + yield s[string_start:string_end], fmt + + in_fmt = False + string_start = i + + if in_fmt: + raise ValueError('end-of-string while parsing format') + + return tuple(_parse_inner()) + + + +@path pyflakes/test +""" +Tests for L{pyflakes.scripts.pyflakes}. +""" + +import contextlib +import io +import os +import sys +import shutil +import subprocess +import tempfile + +from pyflakes.checker import PYPY +from pyflakes.messages import UnusedImport +from pyflakes.reporter import Reporter +from pyflakes.api import ( + main, + check, + checkPath, + checkRecursive, + iterSourceCode, +) +from pyflakes.test.harness import TestCase, skipIf + + +@others +@language python +@tabwidth -4 + +def withStderrTo(stderr, f, *args, **kwargs): + """ + Call C{f} with C{sys.stderr} redirected to C{stderr}. + """ + (outer, sys.stderr) = (sys.stderr, stderr) + try: + return f(*args, **kwargs) + finally: + sys.stderr = outer + + + +class Node: + """ + Mock an AST node. + """ + @others + +class SysStreamCapturing: + """Context manager capturing sys.stdin, sys.stdout and sys.stderr. + + The file handles are replaced with a StringIO object. + """ + + @others + +class LoggingReporter: + """ + Implementation of Reporter that just appends any error to a list. + """ + + @others + +class TestIterSourceCode(TestCase): + """ + Tests for L{iterSourceCode}. + """ + + @others + +class TestReporter(TestCase): + """ + Tests for L{Reporter}. + """ + + @others + +class CheckTests(TestCase): + """ + Tests for L{check} and L{checkPath} which check a file for flakes. + """ + +@others # Must have underindented strings. + + def test_nonDefaultFollowsDefaultSyntaxError(self): + """ + Source which has a non-default argument following a default argument + should include the line number of the syntax error. However these + exceptions do not include an offset. + """ + source = """\ +def foo(bar=baz, bax): + pass +""" + with self.makeTempFile(source) as sourcePath: + if sys.version_info >= (3, 12): + msg = 'parameter without a default follows parameter with a default' # noqa: E501 + else: + msg = 'non-default argument follows default argument' + + if PYPY and sys.version_info >= (3, 9): + column = 18 + elif PYPY: + column = 8 + elif sys.version_info >= (3, 10): + column = 18 + elif sys.version_info >= (3, 9): + column = 21 + else: + column = 9 + last_line = ' ' * (column - 1) + '^\n' + self.assertHasErrors( + sourcePath, + [f"""\ +{sourcePath}:1:{column}: {msg} +def foo(bar=baz, bax): +{last_line}"""] + ) + + + def test_nonKeywordAfterKeywordSyntaxError(self): + """ + Source which has a non-keyword argument after a keyword argument should + include the line number of the syntax error. However these exceptions + do not include an offset. + """ + source = """\ +foo(bar=baz, bax) +""" + with self.makeTempFile(source) as sourcePath: + if sys.version_info >= (3, 9): + column = 17 + elif not PYPY: + column = 14 + else: + column = 13 + last_line = ' ' * (column - 1) + '^\n' + columnstr = '%d:' % column + + message = 'positional argument follows keyword argument' + + self.assertHasErrors( + sourcePath, + ["""\ +{}:1:{} {} +foo(bar=baz, bax) +{}""".format(sourcePath, columnstr, message, last_line)]) + + +class _FieldsOrder(dict): + """Fix order of AST node fields.""" + + def _get_fields(self, node_class): + # handle iter before target, and generators before element + fields = node_class._fields + if 'iter' in fields: + key_first = 'iter'.find + elif 'generators' in fields: + key_first = 'generators'.find + else: + key_first = 'value'.find + return tuple(sorted(fields, key=key_first, reverse=True)) + + def __missing__(self, node_class): + self[node_class] = fields = self._get_fields(node_class) + return fields + + + + def test_invalidEscape(self): + """ + The invalid escape syntax raises ValueError in Python 2 + """ + # ValueError: invalid \x escape + with self.makeTempFile(r"foo = '\xyz'") as sourcePath: + position_end = 1 + if PYPY and sys.version_info >= (3, 9): + column = 7 + elif PYPY: + column = 6 + elif (3, 9) <= sys.version_info < (3, 12): + column = 13 + else: + column = 7 + + last_line = '%s^\n' % (' ' * (column - 1)) + + decoding_error = """\ +%s:1:%d: (unicode error) 'unicodeescape' codec can't decode bytes \ +in position 0-%d: truncated \\xXX escape +foo = '\\xyz' +%s""" % (sourcePath, column, position_end, last_line) + + self.assertHasErrors( + sourcePath, [decoding_error]) + + + @skipIf(sys.platform == 'win32', 'unsupported on Windows') + def test_permissionDenied(self): + """ + If the source file is not readable, this is reported on standard + error. + """ + if os.getuid() == 0: + self.skipTest('root user can access all files regardless of ' + 'permissions') + with self.makeTempFile('') as sourcePath: + os.chmod(sourcePath, 0) + count, errors = self.getErrors(sourcePath) + self.assertEqual(count, 1) + self.assertEqual( + errors, + [('unexpectedError', sourcePath, "Permission denied")]) + + + def test_pyflakesWarning(self): + """ + If the source file has a pyflakes warning, this is reported as a + 'flake'. + """ + with self.makeTempFile("import foo") as sourcePath: + count, errors = self.getErrors(sourcePath) + self.assertEqual(count, 1) + self.assertEqual( + errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))]) + + + def test_encodedFileUTF8(self): + """ + If source file declares the correct encoding, no error is reported. + """ + SNOWMAN = chr(0x2603) + source = ("""\ +# coding: utf-8 +x = "%s" +""" % SNOWMAN).encode('utf-8') + with self.makeTempFile(source) as sourcePath: + self.assertHasErrors(sourcePath, []) + + + def test_CRLFLineEndings(self): + """ + Source files with Windows CR LF line endings are parsed successfully. + """ + with self.makeTempFile("x = 42\r\n") as sourcePath: + self.assertHasErrors(sourcePath, []) + + + def test_misencodedFileUTF8(self): + """ + If a source file contains bytes which cannot be decoded, this is + reported on stderr. + """ + SNOWMAN = chr(0x2603) + source = ("""\ +# coding: ascii +x = "%s" +""" % SNOWMAN).encode('utf-8') + with self.makeTempFile(source) as sourcePath: + self.assertHasErrors( + sourcePath, + [f"{sourcePath}:1:1: 'ascii' codec can't decode byte 0xe2 in position 21: ordinal not in range(128)\n"]) # noqa: E501 + + + def test_misencodedFileUTF16(self): + """ + If a source file contains bytes which cannot be decoded, this is + reported on stderr. + """ + SNOWMAN = chr(0x2603) + source = ("""\ +# coding: ascii +x = "%s" +""" % SNOWMAN).encode('utf-16') + with self.makeTempFile(source) as sourcePath: + if sys.version_info < (3, 11, 4): + expected = f"{sourcePath}: problem decoding source\n" + else: + expected = f"{sourcePath}:1: source code string cannot contain null bytes\n" # noqa: E501 + + self.assertHasErrors(sourcePath, [expected]) + + def test_checkRecursive(self): + """ + L{checkRecursive} descends into each directory, finding Python files + and reporting problems. + """ + tempdir = tempfile.mkdtemp() + try: + os.mkdir(os.path.join(tempdir, 'foo')) + file1 = os.path.join(tempdir, 'foo', 'bar.py') + with open(file1, 'wb') as fd: + fd.write(b"import baz\n") + file2 = os.path.join(tempdir, 'baz.py') + with open(file2, 'wb') as fd: + fd.write(b"import contraband") + log = [] + reporter = LoggingReporter(log) + warnings = checkRecursive([tempdir], reporter) + self.assertEqual(warnings, 2) + self.assertEqual( + sorted(log), + sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))), + ('flake', + str(UnusedImport(file2, Node(1), 'contraband')))])) + finally: + shutil.rmtree(tempdir) + + def test_stdinReportsErrors(self): + """ + L{check} reports syntax errors from stdin + """ + source = "max(1 for i in range(10), key=lambda x: x+1)\n" + err = io.StringIO() + count = withStderrTo(err, check, source, "<stdin>") + self.assertEqual(count, 1) + errlines = err.getvalue().split("\n")[:-1] + + if sys.version_info >= (3, 9): + expected_error = [ + "<stdin>:1:5: Generator expression must be parenthesized", + "max(1 for i in range(10), key=lambda x: x+1)", + " ^", + ] + elif PYPY: + expected_error = [ + "<stdin>:1:4: Generator expression must be parenthesized if not sole argument", # noqa: E501 + "max(1 for i in range(10), key=lambda x: x+1)", + " ^", + ] + else: + expected_error = [ + "<stdin>:1:5: Generator expression must be parenthesized", + ] + + + self.assertEqual(errlines, expected_error) + + + + +class IntegrationTests(TestCase): + """ + Tests of the pyflakes script that actually spawn the script. + """ + @others + +def counter(items): + """ + Simplest required implementation of collections.Counter. Required as 2.6 + does not have Counter in collections. + """ + results = {} + for item in items: + results[item] = results.get(item, 0) + 1 + return results + + + +class TestMain(IntegrationTests): + """ + Tests of the pyflakes main function. + """ + @others + +def __init__(self, lineno, col_offset=0): + self.lineno = lineno + self.col_offset = col_offset + + + +def __init__(self, stdin): + self._stdin = io.StringIO(stdin or '', newline=os.linesep) + + +def __enter__(self): + self._orig_stdin = sys.stdin + self._orig_stdout = sys.stdout + self._orig_stderr = sys.stderr + + sys.stdin = self._stdin + sys.stdout = self._stdout_stringio = io.StringIO(newline=os.linesep) + sys.stderr = self._stderr_stringio = io.StringIO(newline=os.linesep) + + return self + + +def __exit__(self, *args): + self.output = self._stdout_stringio.getvalue() + self.error = self._stderr_stringio.getvalue() + + sys.stdin = self._orig_stdin + sys.stdout = self._orig_stdout + sys.stderr = self._orig_stderr + + + +def __init__(self, log): + """ + Construct a C{LoggingReporter}. + + @param log: A list to append log messages to. + """ + self.log = log + + +def flake(self, message): + self.log.append(('flake', str(message))) + + +def unexpectedError(self, filename, message): + self.log.append(('unexpectedError', filename, message)) + + +def syntaxError(self, filename, msg, lineno, offset, line): + self.log.append(('syntaxError', filename, msg, lineno, offset, line)) + + + +def setUp(self): + self.tempdir = tempfile.mkdtemp() + + +def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): + """ + Yield all direct child nodes of *node*, that is, all fields that + are nodes and all items of fields that are lists of nodes. + + :param node: AST node to be iterated upon + :param omit: String or tuple of strings denoting the + attributes of the node to be omitted from + further parsing + :param _fields_order: Order of AST node fields + """ + for name in _fields_order[node.__class__]: + if omit and name in omit: + continue + field = getattr(node, name, None) + if isinstance(field, ast.AST): + yield field + elif isinstance(field, list): + for item in field: + if isinstance(item, ast.AST): + yield item + + + +def tearDown(self): + shutil.rmtree(self.tempdir) + + +def makeEmptyFile(self, *parts): + assert parts + fpath = os.path.join(self.tempdir, *parts) + open(fpath, 'a').close() + return fpath + + +def test_emptyDirectory(self): + """ + There are no Python files in an empty directory. + """ + self.assertEqual(list(iterSourceCode([self.tempdir])), []) + + +def test_singleFile(self): + """ + If the directory contains one Python file, C{iterSourceCode} will find + it. + """ + childpath = self.makeEmptyFile('foo.py') + self.assertEqual(list(iterSourceCode([self.tempdir])), [childpath]) + + +def test_onlyPythonSource(self): + """ + Files that are not Python source files are not included. + """ + self.makeEmptyFile('foo.pyc') + self.assertEqual(list(iterSourceCode([self.tempdir])), []) + + +def test_recurses(self): + """ + If the Python files are hidden deep down in child directories, we will + find them. + """ + os.mkdir(os.path.join(self.tempdir, 'foo')) + apath = self.makeEmptyFile('foo', 'a.py') + self.makeEmptyFile('foo', 'a.py~') + os.mkdir(os.path.join(self.tempdir, 'bar')) + bpath = self.makeEmptyFile('bar', 'b.py') + cpath = self.makeEmptyFile('c.py') + self.assertEqual( + sorted(iterSourceCode([self.tempdir])), + sorted([apath, bpath, cpath])) + + +def test_shebang(self): + """ + Find Python files that don't end with `.py`, but contain a Python + shebang. + """ + python = os.path.join(self.tempdir, 'a') + with open(python, 'w') as fd: + fd.write('#!/usr/bin/env python\n') + + self.makeEmptyFile('b') + + with open(os.path.join(self.tempdir, 'c'), 'w') as fd: + fd.write('hello\nworld\n') + + python3 = os.path.join(self.tempdir, 'e') + with open(python3, 'w') as fd: + fd.write('#!/usr/bin/env python3\n') + + pythonw = os.path.join(self.tempdir, 'f') + with open(pythonw, 'w') as fd: + fd.write('#!/usr/bin/env pythonw\n') + + python3args = os.path.join(self.tempdir, 'g') + with open(python3args, 'w') as fd: + fd.write('#!/usr/bin/python3 -u\n') + + python3d = os.path.join(self.tempdir, 'i') + with open(python3d, 'w') as fd: + fd.write('#!/usr/local/bin/python3d\n') + + python38m = os.path.join(self.tempdir, 'j') + with open(python38m, 'w') as fd: + fd.write('#! /usr/bin/env python3.8m\n') + + # Should NOT be treated as Python source + notfirst = os.path.join(self.tempdir, 'l') + with open(notfirst, 'w') as fd: + fd.write('#!/bin/sh\n#!/usr/bin/python\n') + + self.assertEqual( + sorted(iterSourceCode([self.tempdir])), + sorted([ + python, python3, pythonw, python3args, python3d, + python38m, + ])) + + +def test_multipleDirectories(self): + """ + L{iterSourceCode} can be given multiple directories. It will recurse + into each of them. + """ + foopath = os.path.join(self.tempdir, 'foo') + barpath = os.path.join(self.tempdir, 'bar') + os.mkdir(foopath) + apath = self.makeEmptyFile('foo', 'a.py') + os.mkdir(barpath) + bpath = self.makeEmptyFile('bar', 'b.py') + self.assertEqual( + sorted(iterSourceCode([foopath, barpath])), + sorted([apath, bpath])) + + +def test_explicitFiles(self): + """ + If one of the paths given to L{iterSourceCode} is not a directory but + a file, it will include that in its output. + """ + epath = self.makeEmptyFile('e.py') + self.assertEqual(list(iterSourceCode([epath])), + [epath]) + + + +def test_syntaxError(self): + """ + C{syntaxError} reports that there was a syntax error in the source + file. It reports to the error stream and includes the filename, line + number, error message, actual line of source and a caret pointing to + where the error is. + """ + err = io.StringIO() + reporter = Reporter(None, err) + reporter.syntaxError('foo.py', 'a problem', 3, 8, 'bad line of source') + self.assertEqual( + ("foo.py:3:8: a problem\n" + "bad line of source\n" + " ^\n"), + err.getvalue()) + + +#!/usr/bin/env python +# Copyright 2005-2011 Divmod, Inc. +# Copyright 2013 Florent Xicluna. See LICENSE file for details +import os.path + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + extra = {'scripts': ["bin/pyflakes"]} +else: + extra = { + 'test_suite': 'pyflakes.test', + 'entry_points': { + 'console_scripts': ['pyflakes = pyflakes.api:main'], + }, + } + + +@others +setup( + name="pyflakes", + license="MIT", + version=get_version(), + description="passive checker of Python programs", + long_description=get_long_description(), + author="A lot of people", + author_email="code-quality@python.org", + url="https://github.com/PyCQA/pyflakes", + packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"], + python_requires='>=3.8', + classifiers=[ + "Development Status :: 6 - Mature", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development", + "Topic :: Utilities", + ], + **extra) +@language python +@tabwidth -4 + +def convert_to_value(item): + if isinstance(item, ast.Constant): + return item.value + elif isinstance(item, ast.Tuple): + return tuple(convert_to_value(i) for i in item.elts) + elif isinstance(item, ast.Name): + return VariableKey(item=item) + else: + return UnhandledKeyType() + + + +def test_syntaxErrorNoOffset(self): + """ + C{syntaxError} doesn't include a caret pointing to the error if + C{offset} is passed as C{None}. + """ + err = io.StringIO() + reporter = Reporter(None, err) + reporter.syntaxError('foo.py', 'a problem', 3, None, + 'bad line of source') + self.assertEqual( + ("foo.py:3: a problem\n" + "bad line of source\n"), + err.getvalue()) + + +def test_syntaxErrorNoText(self): + """ + C{syntaxError} doesn't include text or nonsensical offsets if C{text} is C{None}. + + This typically happens when reporting syntax errors from stdin. + """ + err = io.StringIO() + reporter = Reporter(None, err) + reporter.syntaxError('<stdin>', 'a problem', 0, 0, None) + self.assertEqual(("<stdin>:1:1: a problem\n"), err.getvalue()) + + +def test_multiLineSyntaxError(self): + """ + If there's a multi-line syntax error, then we only report the last + line. The offset is adjusted so that it is relative to the start of + the last line. + """ + err = io.StringIO() + lines = [ + 'bad line of source', + 'more bad lines of source', + ] + reporter = Reporter(None, err) + reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7, + '\n'.join(lines)) + self.assertEqual( + ("foo.py:3:25: a problem\n" + + lines[-1] + "\n" + + " " * 24 + "^\n"), + err.getvalue()) + + +def test_unexpectedError(self): + """ + C{unexpectedError} reports an error processing a source file. + """ + err = io.StringIO() + reporter = Reporter(None, err) + reporter.unexpectedError('source.py', 'error message') + self.assertEqual('source.py: error message\n', err.getvalue()) + + +def test_flake(self): + """ + C{flake} reports a code warning from Pyflakes. It is exactly the + str() of a L{pyflakes.messages.Message}. + """ + out = io.StringIO() + reporter = Reporter(out, None) + message = UnusedImport('foo.py', Node(42), 'bar') + reporter.flake(message) + self.assertEqual(out.getvalue(), f"{message}\n") + + + + @contextlib.contextmanager + def makeTempFile(self, content): + """ + Make a temporary file containing C{content} and return a path to it. + """ + fd, name = tempfile.mkstemp() + try: + with os.fdopen(fd, 'wb') as f: + if not hasattr(content, 'decode'): + content = content.encode('ascii') + f.write(content) + yield name + finally: + os.remove(name) + + + def assertHasErrors(self, path, errorList): + """ + Assert that C{path} causes errors. + + @param path: A path to a file to check. + @param errorList: A list of errors expected to be printed to stderr. + """ + err = io.StringIO() + count = withStderrTo(err, checkPath, path) + self.assertEqual( + (count, err.getvalue()), (len(errorList), ''.join(errorList))) + + + def getErrors(self, path): + """ + Get any warnings or errors reported by pyflakes for the file at C{path}. + + @param path: The path to a Python file on disk that pyflakes will check. + @return: C{(count, log)}, where C{count} is the number of warnings or + errors generated, and log is a list of those warnings, presented + as structured data. See L{LoggingReporter} for more details. + """ + log = [] + reporter = LoggingReporter(log) + count = checkPath(path, reporter) + return count, log + + + def test_legacyScript(self): + from pyflakes.scripts import pyflakes as script_pyflakes + self.assertIs(script_pyflakes.checkPath, checkPath) + + + def test_missingTrailingNewline(self): + """ + Source which doesn't end with a newline shouldn't cause any + exception to be raised nor an error indicator to be returned by + L{check}. + """ + with self.makeTempFile("def foo():\n\tpass\n\t") as fName: + self.assertHasErrors(fName, []) + + +def is_notimplemented_name_node(node): + return isinstance(node, ast.Name) and getNodeName(node) == 'NotImplemented' + + + + def test_checkPathNonExisting(self): + """ + L{checkPath} handles non-existing files. + """ + count, errors = self.getErrors('extremo') + self.assertEqual(count, 1) + self.assertEqual( + errors, + [('unexpectedError', 'extremo', 'No such file or directory')]) + + + def test_multilineSyntaxError(self): + """ + Source which includes a syntax error which results in the raised + L{SyntaxError.text} containing multiple lines of source are reported + with only the last line of that source. + """ + source = """\ +def foo(): + ''' + +def bar(): + pass + +def baz(): + '''quux''' +""" + + # Sanity check - SyntaxError.text should be multiple lines, if it + # isn't, something this test was unprepared for has happened. + def evaluate(source): + exec(source) + try: + evaluate(source) + except SyntaxError as e: + if not PYPY and sys.version_info < (3, 10): + self.assertTrue(e.text.count('\n') > 1) + else: + self.fail() + + with self.makeTempFile(source) as sourcePath: + if PYPY: + message = 'end of file (EOF) while scanning triple-quoted string literal' + elif sys.version_info >= (3, 10): + message = 'unterminated triple-quoted string literal (detected at line 8)' # noqa: E501 + else: + message = 'invalid syntax' + + if PYPY or sys.version_info >= (3, 10): + column = 12 + else: + column = 8 + self.assertHasErrors( + sourcePath, + ["""\ +%s:8:%d: %s + '''quux''' +%s^ +""" % (sourcePath, column, message, ' ' * (column - 1))]) + + def test_eofSyntaxError(self): + """ + The error reported for source files which end prematurely causing a + syntax error reflects the cause for the syntax error. + """ + with self.makeTempFile("def foo(") as sourcePath: + + if PYPY: + msg = 'parenthesis is never closed' + elif sys.version_info >= (3, 10): + msg = "'(' was never closed" + else: + msg = 'unexpected EOF while parsing' + + if PYPY or sys.version_info >= (3, 10): + column = 8 + else: + column = 9 + + spaces = ' ' * (column - 1) + expected = '{}:1:{}: {}\ndef foo(\n{}^\n'.format( + sourcePath, column, msg, spaces + ) + + self.assertHasErrors(sourcePath, [expected]) + + + def test_eofSyntaxErrorWithTab(self): + """ + The error reported for source files which end prematurely causing a + syntax error reflects the cause for the syntax error. + """ + with self.makeTempFile("if True:\n\tfoo =") as sourcePath: + self.assertHasErrors( + sourcePath, + [f"""\ +{sourcePath}:2:7: invalid syntax +\tfoo = +\t ^ +"""]) + + +def setUp(self): + self.tempdir = tempfile.mkdtemp() + self.tempfilepath = os.path.join(self.tempdir, 'temp') + + +def tearDown(self): + shutil.rmtree(self.tempdir) + + +def getPyflakesBinary(self): + """ + Return the path to the pyflakes binary. + """ + import pyflakes + package_dir = os.path.dirname(pyflakes.__file__) + return os.path.join(package_dir, '..', 'bin', 'pyflakes') + + +def runPyflakes(self, paths, stdin=None): + """ + Launch a subprocess running C{pyflakes}. + + @param paths: Command-line arguments to pass to pyflakes. + @param stdin: Text to use as stdin. + @return: C{(returncode, stdout, stderr)} of the completed pyflakes + process. + """ + env = dict(os.environ) + env['PYTHONPATH'] = os.pathsep.join(sys.path) + command = [sys.executable, self.getPyflakesBinary()] + command.extend(paths) + if stdin: + p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = p.communicate(stdin.encode('ascii')) + else: + p = subprocess.Popen(command, env=env, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = p.communicate() + rv = p.wait() + stdout = stdout.decode('utf-8') + stderr = stderr.decode('utf-8') + return (stdout, stderr, rv) + + +def test_goodFile(self): + """ + When a Python source file is all good, the return code is zero and no + messages are printed to either stdout or stderr. + """ + open(self.tempfilepath, 'a').close() + d = self.runPyflakes([self.tempfilepath]) + self.assertEqual(d, ('', '', 0)) + + +def test_fileWithFlakes(self): + """ + When a Python source file has warnings, the return code is non-zero + and the warnings are printed to stdout. + """ + with open(self.tempfilepath, 'wb') as fd: + fd.write(b"import contraband\n") + d = self.runPyflakes([self.tempfilepath]) + expected = UnusedImport(self.tempfilepath, Node(1), 'contraband') + self.assertEqual(d, (f"{expected}{os.linesep}", '', 1)) + + +class Binding: + """ + Represents the binding of a value to a name. + + The checker uses this to keep track of which names have been bound and + which names have not. See L{Assignment} for a special type of binding that + is checked with stricter rules. + + @ivar used: pair of (L{Scope}, node) indicating the scope and + the node that this binding was last used. + """ + + def __init__(self, name, source): + self.name = name + self.source = source + self.used = False + + def __str__(self): + return self.name + + def __repr__(self): + return '<{} object {!r} from line {!r} at 0x{:x}>'.format( + self.__class__.__name__, + self.name, + self.source.lineno, + id(self), + ) + + def redefines(self, other): + return isinstance(other, Definition) and self.name == other.name + + + +def test_errors_io(self): + """ + When pyflakes finds errors with the files it's given, (if they don't + exist, say), then the return code is non-zero and the errors are + printed to stderr. + """ + d = self.runPyflakes([self.tempfilepath]) + error_msg = '{}: No such file or directory{}'.format(self.tempfilepath, + os.linesep) + self.assertEqual(d, ('', error_msg, 1)) + + +def test_errors_syntax(self): + """ + When pyflakes finds errors with the files it's given, (if they don't + exist, say), then the return code is non-zero and the errors are + printed to stderr. + """ + with open(self.tempfilepath, 'wb') as fd: + fd.write(b"import") + d = self.runPyflakes([self.tempfilepath]) + error_msg = '{0}:1:7: invalid syntax{1}import{1} ^{1}'.format( + self.tempfilepath, os.linesep) + self.assertEqual(d, ('', error_msg, 1)) + + +def test_readFromStdin(self): + """ + If no arguments are passed to C{pyflakes} then it reads from stdin. + """ + d = self.runPyflakes([], stdin='import contraband') + expected = UnusedImport('<stdin>', Node(1), 'contraband') + self.assertEqual(d, (f"{expected}{os.linesep}", '', 1)) + + + +def runPyflakes(self, paths, stdin=None): + try: + with SysStreamCapturing(stdin) as capture: + main(args=paths) + except SystemExit as e: + self.assertIsInstance(e.code, bool) + rv = int(e.code) + return (capture.output, capture.error, rv) + else: + raise RuntimeError('SystemExit not raised') + +@path pyflakes/test +""" +Tests for detecting redefinition of builtins. +""" +from pyflakes import messages as m +from pyflakes.test.harness import TestCase + + +@others + ''') +@language python +@tabwidth -4 + +class TestBuiltins(TestCase): + + @others + + ''', m.UndefinedLocal) + + def test_global_shadowing_builtin(self): + self.flakes(''' + def f(): + global range + range = None + print(range) + + f() + +def test_builtin_unbound_local(self): + self.flakes(''' + def foo(): + a = range(1, 10) + range = a + return range + + foo() + + print(range) + +@path pyflakes/test +from pyflakes import messages as m +from pyflakes.checker import (FunctionScope, ClassScope, ModuleScope, + Argument, FunctionDefinition, Assignment) +from pyflakes.test.harness import TestCase + + +@others +@language python +@tabwidth -4 + +class TestCodeSegments(TestCase): + """ + Tests for segments of a module + """ + + @others + +class Definition(Binding): + """ + A binding that defines a function or a class. + """ + @others + + ''', is_segment=True) + + self.flakes(''' + def foo(): + def bar(): + x = 0 + ''', m.UnusedVariable, is_segment=True) + + def test_class_segment(self): + self.flakes(''' + class Foo: + class Bar: + pass + + ''', is_segment=True) + + self.flakes(''' + class Foo: + def bar(): + x = 0 + ''', m.UnusedVariable, is_segment=True) + + def test_scope_class(self): + checker = self.flakes(''' + class Foo: + x = 0 + def bar(a, b=1, *d, **e): + pass + + ''', is_segment=True) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + class_scopes = [ + scope for scope in scopes if scope.__class__ is ClassScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + # Ensure module scope is not present because we are analysing + # the inner contents of Foo + self.assertEqual(len(module_scopes), 0) + self.assertEqual(len(class_scopes), 1) + self.assertEqual(len(function_scopes), 1) + + class_scope = class_scopes[0] + function_scope = function_scopes[0] + + self.assertIsInstance(class_scope, ClassScope) + self.assertIsInstance(function_scope, FunctionScope) + + self.assertIn('x', class_scope) + self.assertIn('bar', class_scope) + + self.assertIn('a', function_scope) + self.assertIn('b', function_scope) + self.assertIn('d', function_scope) + self.assertIn('e', function_scope) + + self.assertIsInstance(class_scope['bar'], FunctionDefinition) + self.assertIsInstance(class_scope['x'], Assignment) + + self.assertIsInstance(function_scope['a'], Argument) + self.assertIsInstance(function_scope['b'], Argument) + self.assertIsInstance(function_scope['d'], Argument) + self.assertIsInstance(function_scope['e'], Argument) + + def test_scope_function(self): + checker = self.flakes(''' + def foo(a, b=1, *d, **e): + def bar(f, g=1, *h, **i): + pass + + ''', is_segment=True) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + # Ensure module scope is not present because we are analysing + # the inner contents of foo + self.assertEqual(len(module_scopes), 0) + self.assertEqual(len(function_scopes), 2) + + function_scope_foo = function_scopes[1] + function_scope_bar = function_scopes[0] + + self.assertIsInstance(function_scope_foo, FunctionScope) + self.assertIsInstance(function_scope_bar, FunctionScope) + + self.assertIn('a', function_scope_foo) + self.assertIn('b', function_scope_foo) + self.assertIn('d', function_scope_foo) + self.assertIn('e', function_scope_foo) + self.assertIn('bar', function_scope_foo) + + self.assertIn('f', function_scope_bar) + self.assertIn('g', function_scope_bar) + self.assertIn('h', function_scope_bar) + self.assertIn('i', function_scope_bar) + + self.assertIsInstance(function_scope_foo['bar'], FunctionDefinition) + self.assertIsInstance(function_scope_foo['a'], Argument) + self.assertIsInstance(function_scope_foo['b'], Argument) + self.assertIsInstance(function_scope_foo['d'], Argument) + self.assertIsInstance(function_scope_foo['e'], Argument) + + self.assertIsInstance(function_scope_bar['f'], Argument) + self.assertIsInstance(function_scope_bar['g'], Argument) + self.assertIsInstance(function_scope_bar['h'], Argument) + self.assertIsInstance(function_scope_bar['i'], Argument) + + def test_scope_async_function(self): + self.flakes('async def foo(): pass', is_segment=True) + +def test_function_segment(self): + self.flakes(''' + def foo(): + def bar(): + pass + +@path pyflakes/test +""" +Tests for dict duplicate keys Pyflakes behavior. +""" + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase + + +@others + ''') +@language python +@tabwidth -4 + +class Test(TestCase): + + @others + + ''', + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_keys_in_lambda(self): + self.flakes( + "lambda x: {(0,1): 1, (0,1): 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + + def test_duplicate_keys_tuples(self): + self.flakes( + "{(0,1): 1, (0,1): 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + + def test_duplicate_keys_tuples_int_and_float(self): + self.flakes( + "{(0,1): 1, (0,1.0): 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + +class Builtin(Definition): + """A definition created for all Python builtins.""" + + @others + + def test_duplicate_keys_ints(self): + self.flakes( + "{1: 1, 1: 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + + def test_duplicate_keys_bools(self): + self.flakes( + "{True: 1, True: 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + + def test_duplicate_keys_bools_false(self): + # Needed to ensure 2.x correctly coerces these from variables + self.flakes( + "{False: 1, False: 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + + def test_duplicate_keys_none(self): + self.flakes( + "{None: 1, None: 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + + def test_duplicate_variable_keys(self): + self.flakes( + ''' + a = 1 + {a: 1, a: 2} + + ''', + m.MultiValueRepeatedKeyVariable, + m.MultiValueRepeatedKeyVariable, + ) + + def test_duplicate_variable_values(self): + self.flakes( + ''' + a = 1 + b = 2 + {1: a, 1: b} + + ''', + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_variable_values_same_value(self): + # Current behaviour is not to look up variable values. This is to + # confirm that. + self.flakes( + ''' + a = 1 + b = 1 + {1: a, 1: b} + + ''', + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_key_float_and_int(self): + """ + These do look like different values, but when it comes to their use as + keys, they compare as equal and so are actually duplicates. + The literal dict {1: 1, 1.0: 1} actually becomes {1.0: 1}. + """ + self.flakes( + ''' + {1: 1, 1.0: 2} + + ''', + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_no_duplicate_key_error_same_value(self): + self.flakes(''' + {'yes': 1, 'yes': 1} + + ''') + + def test_no_duplicate_key_errors(self): + self.flakes(''' + {'yes': 1, 'no': 2} + +class UnhandledKeyType: + """ + A dictionary key of a type that we cannot or do not check for duplicates. + """ + + + + ''') + + def test_no_duplicate_keys_tuples_same_first_element(self): + self.flakes("{(0,1): 1, (0,2): 1}") + + + def test_no_duplicate_key_errors_func_call(self): + self.flakes(''' + def test(thing): + pass + test({True: 1, None: 2, False: 1}) + + ''') + + def test_no_duplicate_key_errors_bool_or_none(self): + self.flakes("{True: 1, None: 2, False: 1}") + + + def test_no_duplicate_key_errors_ints(self): + self.flakes(''' + {1: 1, 2: 1} + + ''') + + def test_no_duplicate_key_errors_vars(self): + self.flakes(''' + test = 'yes' + rest = 'yes' + {test: 1, rest: 2} + + ''') + + def test_no_duplicate_key_errors_tuples(self): + self.flakes(''' + {(0,1): 1, (0,2): 1} + + ''') + + def test_no_duplicate_key_errors_instance_attributes(self): + self.flakes(''' + class Test(): + pass + f = Test() + f.a = 1 + {f.a: 1, f.a: 1} + +def test_duplicate_keys(self): + self.flakes( + "{'yes': 1, 'yes': 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + +def test_duplicate_keys_bytes_vs_unicode_py3(self): + self.flakes("{b'a': 1, u'a': 2}") + + +def test_duplicate_values_bytes_vs_unicode_py3(self): + self.flakes( + "{1: b'a', 1: u'a'}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + +class VariableKey: + """ + A dictionary key which is a variable. + + @ivar item: The variable AST object. + """ + @others + +def test_multiple_duplicate_keys(self): + self.flakes( + "{'yes': 1, 'yes': 2, 'no': 2, 'no': 3}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + +def test_duplicate_keys_in_function(self): + self.flakes( + ''' + def f(thing): + pass + f({'yes': 1, 'yes': 2}) + +@path pyflakes/test +import textwrap + +from pyflakes import messages as m +from pyflakes.checker import ( + PYPY, + DoctestScope, + FunctionScope, + ModuleScope, +) +from pyflakes.test.test_other import Test as TestOther +from pyflakes.test.test_imports import Test as TestImports +from pyflakes.test.test_undefined_names import Test as TestUndefinedNames +from pyflakes.test.harness import TestCase, skip + + +@others +@language python +@tabwidth -4 + +class _DoctestMixin: + + @others + + ''') + return doctestificator % "\n ".join(lines) + + def flakes(self, input, *args, **kw): + return super().flakes(self.doctestify(input), *args, **kw) + + + +class Test(TestCase): + + @others + + """) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + doctest_scopes = [ + scope for scope in scopes if scope.__class__ is DoctestScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + self.assertEqual(len(module_scopes), 1) + self.assertEqual(len(doctest_scopes), 1) + + module_scope = module_scopes[0] + doctest_scope = doctest_scopes[0] + + self.assertIsInstance(doctest_scope, DoctestScope) + self.assertIsInstance(doctest_scope, ModuleScope) + self.assertNotIsInstance(doctest_scope, FunctionScope) + self.assertNotIsInstance(module_scope, DoctestScope) + + self.assertIn('m', module_scope) + self.assertIn('doctest_stuff', module_scope) + + self.assertIn('d', doctest_scope) + + self.assertEqual(len(function_scopes), 1) + self.assertIn('f', function_scopes[0]) + + def test_nested_doctest_ignored(self): + """Check that nested doctests are ignored.""" + checker = self.flakes(""" + m = None + + def doctest_stuff(): + ''' + >>> def function_in_doctest(): + ... \"\"\" + ... >>> ignored_undefined_name + ... \"\"\" + ... df = m + ... return df + ... + >>> function_in_doctest() + ''' + f = m + return f + + """) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + doctest_scopes = [ + scope for scope in scopes if scope.__class__ is DoctestScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + self.assertEqual(len(module_scopes), 1) + self.assertEqual(len(doctest_scopes), 1) + + module_scope = module_scopes[0] + doctest_scope = doctest_scopes[0] + + self.assertIn('m', module_scope) + self.assertIn('doctest_stuff', module_scope) + self.assertIn('function_in_doctest', doctest_scope) + + self.assertEqual(len(function_scopes), 2) + + self.assertIn('f', function_scopes[0]) + self.assertIn('df', function_scopes[1]) + + def test_global_module_scope_pollution(self): + """Check that global in doctest does not pollute module scope.""" + checker = self.flakes(""" + def doctest_stuff(): + ''' + >>> def function_in_doctest(): + ... global m + ... m = 50 + ... df = 10 + ... m = df + ... + >>> function_in_doctest() + ''' + f = 10 + return f + + + """) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + doctest_scopes = [ + scope for scope in scopes if scope.__class__ is DoctestScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + self.assertEqual(len(module_scopes), 1) + self.assertEqual(len(doctest_scopes), 1) + + module_scope = module_scopes[0] + doctest_scope = doctest_scopes[0] + + self.assertIn('doctest_stuff', module_scope) + self.assertIn('function_in_doctest', doctest_scope) + + self.assertEqual(len(function_scopes), 2) + + self.assertIn('f', function_scopes[0]) + self.assertIn('df', function_scopes[1]) + self.assertIn('m', function_scopes[1]) + + self.assertNotIn('m', module_scope) + + def test_global_undefined(self): + self.flakes(""" + global m + + def doctest_stuff(): + ''' + >>> m + ''' + + """, m.UndefinedName) + + def test_nested_class(self): + """Doctest within nested class are processed.""" + self.flakes(""" + class C: + class D: + ''' + >>> m + ''' + def doctest_stuff(self): + ''' + >>> m + ''' + return 1 + +class Importation(Definition): + """ + A binding created by an import statement. + + @ivar fullName: The complete name given to the import statement, + possibly including multiple dotted components. + @type fullName: C{str} + """ + + @others + + """, m.UndefinedName, m.UndefinedName) + + def test_ignore_nested_function(self): + """Doctest module does not process doctest in nested functions.""" + # 'syntax error' would cause a SyntaxError if the doctest was processed. + # However doctest does not find doctest in nested functions + # (https://bugs.python.org/issue1650090). If nested functions were + # processed, this use of m should cause UndefinedName, and the + # name inner_function should probably exist in the doctest scope. + self.flakes(""" + def doctest_stuff(): + def inner_function(): + ''' + >>> syntax error + >>> inner_function() + 1 + >>> m + ''' + return 1 + m = inner_function() + return m + + """) + + def test_inaccessible_scope_class(self): + """Doctest may not access class scope.""" + self.flakes(""" + class C: + def doctest_stuff(self): + ''' + >>> m + ''' + return 1 + m = 1 + + """, m.UndefinedName) + + def test_importBeforeDoctest(self): + self.flakes(""" + import foo + + def doctest_stuff(): + ''' + >>> foo + ''' + + """) + + @skip("todo") + def test_importBeforeAndInDoctest(self): + self.flakes(''' + import foo + + def doctest_stuff(): + """ + >>> import foo + >>> foo + """ + + foo + + ''', m.RedefinedWhileUnused) + + def test_importInDoctestAndAfter(self): + self.flakes(''' + def doctest_stuff(): + """ + >>> import foo + >>> foo + """ + + import foo + foo() + + ''') + + def test_offsetInDoctests(self): + exc = self.flakes(''' + + def doctest_stuff(): + """ + >>> x # line 5 + """ + + + ''', m.UndefinedName).messages[0] + self.assertEqual(exc.lineno, 5) + self.assertEqual(exc.col, 12) + + def test_offsetInLambdasInDoctests(self): + exc = self.flakes(''' + + def doctest_stuff(): + """ + >>> lambda: x # line 5 + """ + + + ''', m.UndefinedName).messages[0] + self.assertEqual(exc.lineno, 5) + self.assertEqual(exc.col, 20) + + def test_offsetAfterDoctests(self): + exc = self.flakes(''' + + def doctest_stuff(): + """ + >>> x = 5 + """ + + x + + + ''', m.UndefinedName).messages[0] + self.assertEqual(exc.lineno, 8) + self.assertEqual(exc.col, 0) + + def test_syntaxErrorInDoctest(self): + exceptions = self.flakes( + ''' + def doctest_stuff(): + """ + >>> from # line 4 + >>> fortytwo = 42 + >>> except Exception: + """ + + ''', + m.DoctestSyntaxError, + m.DoctestSyntaxError, + m.DoctestSyntaxError).messages + exc = exceptions[0] + self.assertEqual(exc.lineno, 4) + if not PYPY: + self.assertEqual(exc.col, 18) + else: + self.assertEqual(exc.col, 26) + + # PyPy error column offset is 0, + # for the second and third line of the doctest + # i.e. at the beginning of the line + exc = exceptions[1] + self.assertEqual(exc.lineno, 5) + if PYPY: + self.assertEqual(exc.col, 13) + else: + self.assertEqual(exc.col, 16) + exc = exceptions[2] + self.assertEqual(exc.lineno, 6) + self.assertEqual(exc.col, 13) + + def test_indentationErrorInDoctest(self): + exc = self.flakes(''' + def doctest_stuff(): + """ + >>> if True: + ... pass + """ + +class SubmoduleImportation(Importation): + """ + A binding created by a submodule import statement. + + A submodule import is a special case where the root module is implicitly + imported, without an 'as' clause, and the submodule is also imported. + Python does not restrict which attributes of the root module may be used. + + This class is only used when the submodule import is without an 'as' clause. + + pyflakes handles this case by registering the root module name in the scope, + allowing any attribute of the root module to be accessed. + + RedefinedWhileUnused is suppressed in `redefines` unless the submodule + name is also the same, to avoid false positives. + """ + + @others + + ''', m.DoctestSyntaxError).messages[0] + self.assertEqual(exc.lineno, 5) + self.assertEqual(exc.col, 13) + + def test_offsetWithMultiLineArgs(self): + (exc1, exc2) = self.flakes( + ''' + def doctest_stuff(arg1, + arg2, + arg3): + """ + >>> assert + >>> this + """ + + ''', + m.DoctestSyntaxError, + m.UndefinedName).messages + self.assertEqual(exc1.lineno, 6) + self.assertEqual(exc1.col, 19) + self.assertEqual(exc2.lineno, 7) + self.assertEqual(exc2.col, 12) + + def test_doctestCanReferToFunction(self): + self.flakes(""" + def foo(): + ''' + >>> foo + ''' + + """) + + def test_doctestCanReferToClass(self): + self.flakes(""" + class Foo(): + ''' + >>> Foo + ''' + def bar(self): + ''' + >>> Foo + ''' + + """) + + def test_noOffsetSyntaxErrorInDoctest(self): + exceptions = self.flakes( + ''' + def buildurl(base, *args, **kwargs): + """ + >>> buildurl('/blah.php', ('a', '&'), ('b', '=') + '/blah.php?a=%26&b=%3D' + >>> buildurl('/blah.php', a='&', 'b'='=') + '/blah.php?b=%3D&a=%26' + """ + pass + + ''', + m.DoctestSyntaxError, + m.DoctestSyntaxError).messages + exc = exceptions[0] + self.assertEqual(exc.lineno, 4) + exc = exceptions[1] + self.assertEqual(exc.lineno, 6) + + def test_singleUnderscoreInDoctest(self): + self.flakes(''' + def func(): + """A docstring + + >>> func() + 1 + >>> _ + 1 + """ + return 1 + + ''') + + def test_globalUnderscoreInDoctest(self): + self.flakes(""" + from gettext import ugettext as _ + + def doctest_stuff(): + ''' + >>> pass + ''' + + """, m.UnusedImport) + + +class TestOther(_DoctestMixin, TestOther): + """Run TestOther with each test wrapped in a doctest.""" + + + +class TestImports(_DoctestMixin, TestImports): + """Run TestImports with each test wrapped in a doctest.""" + + + +class TestUndefinedNames(_DoctestMixin, TestUndefinedNames): + """Run TestUndefinedNames with each test wrapped in a doctest.""" + +withDoctest = True + +def doctestify(self, input): + lines = [] + for line in textwrap.dedent(input).splitlines(): + if line.strip() == '': + pass + elif (line.startswith(' ') or + line.startswith('except:') or + line.startswith('except ') or + line.startswith('finally:') or + line.startswith('else:') or + line.startswith('elif ') or + (lines and lines[-1].startswith(('>>> @', '... @')))): + line = "... %s" % line + else: + line = ">>> %s" % line + lines.append(line) + doctestificator = textwrap.dedent('''\ + def doctest_something(): + """ + %s + """ + +class ImportationFrom(Importation): + + @others + +withDoctest = True + +def test_scope_class(self): + """Check that a doctest is given a DoctestScope.""" + checker = self.flakes(""" + m = None + + def doctest_stuff(): + ''' + >>> d = doctest_stuff() + ''' + f = m + return f + +@path pyflakes/test +from pyflakes import messages as m +from pyflakes.checker import ( + FutureImportation, + Importation, + ImportationFrom, + StarImportation, + SubmoduleImportation, +) +from pyflakes.test.harness import TestCase, skip + + +@others + ''') + + self.flakes(''' + from interior import decorate + @decorate("foo") + class bar: + pass + ''') + + self.flakes(''' + @decorate + class foo: + pass + ''', m.UndefinedName) +@language python +@tabwidth -4 + +class TestImportationObject(TestCase): + + @others + +class Test(TestCase): + + @others + + os.path''', m.RedefinedWhileUnused) + + def test_redefinedIfElse(self): + """ + Test that importing a module twice in if + and else blocks does not raise a warning. + """ + self.flakes(''' + i = 2 + if i==1: + import os + else: + import os + + os.path''') + + def test_redefinedTry(self): + """ + Test that importing a module twice in a try block + does raise a warning. + """ + self.flakes(''' + try: + import os + import os + except: + pass + + os.path''', m.RedefinedWhileUnused) + + def test_redefinedTryExcept(self): + """ + Test that importing a module twice in a try + and except block does not raise a warning. + """ + self.flakes(''' + try: + import os + except: + import os + + os.path''') + + def test_redefinedTryNested(self): + """ + Test that importing a module twice using a nested + try/except and if blocks does not issue a warning. + """ + self.flakes(''' + try: + if True: + if True: + import os + except: + import os + + os.path''') + + def test_redefinedTryExceptMulti(self): + self.flakes(""" + try: + from aa import mixer + except AttributeError: + from bb import mixer + except RuntimeError: + from cc import mixer + except: + from dd import mixer + mixer(123) + + """) + + def test_redefinedTryElse(self): + self.flakes(""" + try: + from aa import mixer + except ImportError: + pass + else: + from bb import mixer + mixer(123) + +def get_version(fname=os.path.join('pyflakes', '__init__.py')): + with open(fname) as f: + for line in f: + if line.startswith('__version__'): + return eval(line.split('=')[-1]) + + + +class StarImportation(Importation): + """A binding created by a 'from x import *' statement.""" + + @others + + """, m.RedefinedWhileUnused) + + def test_redefinedTryExceptElse(self): + self.flakes(""" + try: + import funca + except ImportError: + from bb import funca + from bb import funcb + else: + from bbb import funcb + print(funca, funcb) + + """) + + def test_redefinedTryExceptFinally(self): + self.flakes(""" + try: + from aa import a + except ImportError: + from bb import a + finally: + a = 42 + print(a) + + """) + + def test_redefinedTryExceptElseFinally(self): + self.flakes(""" + try: + import b + except ImportError: + b = Ellipsis + from bb import a + else: + from aa import a + finally: + a = 42 + print(a, b) + + """) + + def test_redefinedByFunction(self): + self.flakes(''' + import fu + def fu(): + pass + + ''', m.RedefinedWhileUnused) + + def test_redefinedInNestedFunction(self): + """ + Test that shadowing a global name with a nested function definition + generates a warning. + """ + self.flakes(''' + import fu + def bar(): + def baz(): + def fu(): + pass + + ''', m.RedefinedWhileUnused, m.UnusedImport) + + def test_redefinedInNestedFunctionTwice(self): + """ + Test that shadowing a global name with a nested function definition + generates a warning. + """ + self.flakes(''' + import fu + def bar(): + import fu + def baz(): + def fu(): + pass + + ''', + m.RedefinedWhileUnused, m.RedefinedWhileUnused, + m.UnusedImport, m.UnusedImport) + + def test_redefinedButUsedLater(self): + """ + Test that a global import which is redefined locally, + but used later in another scope does not generate a warning. + """ + self.flakes(''' + import unittest, transport + + class GetTransportTestCase(unittest.TestCase): + def test_get_transport(self): + transport = 'transport' + self.assertIsNotNone(transport) + + class TestTransportMethodArgs(unittest.TestCase): + def test_send_defaults(self): + transport.Transport() + + ''') + + def test_redefinedByClass(self): + self.flakes(''' + import fu + class fu: + pass + + ''', m.RedefinedWhileUnused) + + def test_redefinedBySubclass(self): + """ + If an imported name is redefined by a class statement which also uses + that name in the bases list, no warning is emitted. + """ + self.flakes(''' + from fu import bar + class bar(bar): + pass + + ''') + + def test_redefinedInClass(self): + """ + Test that shadowing a global with a class attribute does not produce a + warning. + """ + self.flakes(''' + import fu + class bar: + fu = 1 + print(fu) + +class FutureImportation(ImportationFrom): + """ + A binding created by a from `__future__` import statement. + + `__future__` imports are implicitly used. + """ + + @others + + ''') + + def test_importInClass(self): + """ + Test that import within class is a locally scoped attribute. + """ + self.flakes(''' + class bar: + import fu + + ''') + + self.flakes(''' + class bar: + import fu + + fu + ''', m.UndefinedName) + + def test_usedInFunction(self): + self.flakes(''' + import fu + def fun(): + print(fu) + + ''') + + def test_shadowedByParameter(self): + self.flakes(''' + import fu + def fun(fu): + print(fu) + + ''', m.UnusedImport, m.RedefinedWhileUnused) + + self.flakes(''' + import fu + def fun(fu): + print(fu) + print(fu) + ''') + + def test_newAssignment(self): + self.flakes('fu = None') + + + def test_usedInGetattr(self): + self.flakes('import fu; fu.bar.baz') + self.flakes('import fu; "bar".fu.baz', m.UnusedImport) + + + def test_usedInSlice(self): + self.flakes('import fu; print(fu.bar[1:])') + + + def test_usedInIfBody(self): + self.flakes(''' + import fu + if True: print(fu) + + ''') + + def test_usedInIfConditional(self): + self.flakes(''' + import fu + if fu: pass + + ''') + + def test_usedInElifConditional(self): + self.flakes(''' + import fu + if False: pass + elif fu: pass + + ''') + + def test_usedInElse(self): + self.flakes(''' + import fu + if False: pass + else: print(fu) + +class Argument(Binding): + """ + Represents binding a name as an argument. + """ + + + + ''') + + def test_usedInCall(self): + self.flakes('import fu; fu.bar()') + + + def test_usedInClass(self): + self.flakes(''' + import fu + class bar: + bar = fu + + ''') + + def test_usedInClassBase(self): + self.flakes(''' + import fu + class bar(object, fu.baz): + pass + + ''') + + def test_notUsedInNestedScope(self): + self.flakes(''' + import fu + def bleh(): + pass + print(fu) + + ''') + + def test_usedInFor(self): + self.flakes(''' + import fu + for bar in range(9): + print(fu) + + ''') + + def test_usedInForElse(self): + self.flakes(''' + import fu + for bar in range(10): + pass + else: + print(fu) + + ''') + + def test_redefinedByFor(self): + self.flakes(''' + import fu + for fu in range(2): + pass + + ''', m.ImportShadowedByLoopVar) + + def test_shadowedByFor(self): + """ + Test that shadowing a global name with a for loop variable generates a + warning. + """ + self.flakes(''' + import fu + fu.bar() + for fu in (): + pass + + ''', m.ImportShadowedByLoopVar) + + def test_shadowedByForDeep(self): + """ + Test that shadowing a global name with a for loop variable nested in a + tuple unpack generates a warning. + """ + self.flakes(''' + import fu + fu.bar() + for (x, y, z, (a, b, c, (fu,))) in (): + pass + + ''', m.ImportShadowedByLoopVar) + # Same with a list instead of a tuple + self.flakes(''' + import fu + fu.bar() + for [x, y, z, (a, b, c, (fu,))] in (): + pass + ''', m.ImportShadowedByLoopVar) + + def test_usedInReturn(self): + self.flakes(''' + import fu + def fun(): + return fu + +class Assignment(Binding): + """ + Represents binding a name with an explicit assignment. + + The checker will raise warnings for any Assignment that isn't used. Also, + the checker does not consider assignments in tuple/list unpacking to be + Assignments, rather it treats them as simple Bindings. + """ + + +class NamedExprAssignment(Assignment): + """ + Represents binding a name with an assignment expression. + """ + + + + ''') + + def test_usedInOperators(self): + self.flakes('import fu; 3 + fu.bar') + self.flakes('import fu; 3 % fu.bar') + self.flakes('import fu; 3 - fu.bar') + self.flakes('import fu; 3 * fu.bar') + self.flakes('import fu; 3 ** fu.bar') + self.flakes('import fu; 3 / fu.bar') + self.flakes('import fu; 3 // fu.bar') + self.flakes('import fu; -fu.bar') + self.flakes('import fu; ~fu.bar') + self.flakes('import fu; 1 == fu.bar') + self.flakes('import fu; 1 | fu.bar') + self.flakes('import fu; 1 & fu.bar') + self.flakes('import fu; 1 ^ fu.bar') + self.flakes('import fu; 1 >> fu.bar') + self.flakes('import fu; 1 << fu.bar') + + + def test_usedInAssert(self): + self.flakes('import fu; assert fu.bar') + + + def test_usedInSubscript(self): + self.flakes('import fu; fu.bar[1]') + + + def test_usedInLogic(self): + self.flakes('import fu; fu and False') + self.flakes('import fu; fu or False') + self.flakes('import fu; not fu.bar') + + + def test_usedInList(self): + self.flakes('import fu; [fu]') + + + def test_usedInTuple(self): + self.flakes('import fu; (fu,)') + + + def test_usedInTry(self): + self.flakes(''' + import fu + try: fu + except: pass + + ''') + + def test_usedInExcept(self): + self.flakes(''' + import fu + try: fu + except: pass + + ''') + + def test_redefinedByExcept(self): + expected = [m.RedefinedWhileUnused] + # The exc variable is unused inside the exception handler. + expected.append(m.UnusedVariable) + self.flakes(''' + import fu + try: pass + except Exception as fu: pass + + ''', *expected) + + def test_usedInRaise(self): + self.flakes(''' + import fu + raise fu.bar + + ''') + + def test_usedInYield(self): + self.flakes(''' + import fu + def gen(): + yield fu + + ''') + + def test_usedInDict(self): + self.flakes('import fu; {fu:None}') + self.flakes('import fu; {1:fu}') + + + def test_usedInParameterDefault(self): + self.flakes(''' + import fu + def f(bar=fu): + pass + + ''') + + def test_usedInAttributeAssign(self): + self.flakes('import fu; fu.bar = 1') + + + def test_usedInKeywordArg(self): + self.flakes('import fu; fu.bar(stuff=fu)') + + + def test_usedInAssignment(self): + self.flakes('import fu; bar=fu') + self.flakes('import fu; n=0; n+=fu') + + + def test_usedInListComp(self): + self.flakes('import fu; [fu for _ in range(1)]') + self.flakes('import fu; [1 for _ in range(1) if fu]') + + + def test_usedInTryFinally(self): + self.flakes(''' + import fu + try: pass + finally: fu + + ''') + + self.flakes(''' + import fu + try: fu + finally: pass + ''') + + def test_usedInWhile(self): + self.flakes(''' + import fu + while 0: + fu + + ''') + + self.flakes(''' + import fu + while fu: pass + ''') + + def test_usedInGlobal(self): + """ + A 'global' statement shadowing an unused import should not prevent it + from being reported. + """ + self.flakes(''' + import fu + def f(): global fu + +class Annotation(Binding): + """ + Represents binding a name to a type without an associated value. + + As long as this name is not assigned a value in another binding, it is considered + undefined for most purposes. One notable exception is using the name as a type + annotation. + """ + + @others + + ''', m.UnusedImport) + + def test_usedAndGlobal(self): + """ + A 'global' statement shadowing a used import should not cause it to be + reported as unused. + """ + self.flakes(''' + import foo + def f(): global foo + def g(): foo.is_used() + + ''') + + def test_assignedToGlobal(self): + """ + Binding an import to a declared global should not cause it to be + reported as unused. + """ + self.flakes(''' + def f(): global foo; import foo + def g(): foo.is_used() + + ''') + + def test_usedInExec(self): + exec_stmt = 'exec("print(1)", fu.bar)' + self.flakes('import fu; %s' % exec_stmt) + + + def test_usedInLambda(self): + self.flakes('import fu; lambda: fu') + + + def test_shadowedByLambda(self): + self.flakes('import fu; lambda fu: fu', + m.UnusedImport, m.RedefinedWhileUnused) + self.flakes('import fu; lambda fu: fu\nfu()') + + + def test_usedInSliceObj(self): + self.flakes('import fu; "meow"[::fu]') + + + def test_unusedInNestedScope(self): + self.flakes(''' + def bar(): + import fu + fu + + ''', m.UnusedImport, m.UndefinedName) + + def test_methodsDontUseClassScope(self): + self.flakes(''' + class bar: + import fu + def fun(self): + fu + + ''', m.UndefinedName) + + def test_nestedFunctionsNestScope(self): + self.flakes(''' + def a(): + def b(): + fu + import fu + + ''') + + def test_nestedClassAndFunctionScope(self): + self.flakes(''' + def a(): + import fu + class b: + def c(self): + print(fu) + +class FunctionDefinition(Definition): + pass + + + + ''') + + def test_importStar(self): + """Use of import * at module level is reported.""" + self.flakes('from fu import *', m.ImportStarUsed, m.UnusedImport) + self.flakes(''' + try: + from fu import * + except: + pass + + ''', m.ImportStarUsed, m.UnusedImport) + + checker = self.flakes('from fu import *', + m.ImportStarUsed, m.UnusedImport) + + error = checker.messages[0] + assert error.message.startswith("'from %s import *' used; unable ") + assert error.message_args == ('fu', ) + + error = checker.messages[1] + assert error.message == '%r imported but unused' + assert error.message_args == ('fu.*', ) + + def test_importStar_relative(self): + """Use of import * from a relative import is reported.""" + self.flakes('from .fu import *', m.ImportStarUsed, m.UnusedImport) + self.flakes(''' + try: + from .fu import * + except: + pass + + ''', m.ImportStarUsed, m.UnusedImport) + + checker = self.flakes('from .fu import *', + m.ImportStarUsed, m.UnusedImport) + + error = checker.messages[0] + assert error.message.startswith("'from %s import *' used; unable ") + assert error.message_args == ('.fu', ) + + error = checker.messages[1] + assert error.message == '%r imported but unused' + assert error.message_args == ('.fu.*', ) + + checker = self.flakes('from .. import *', + m.ImportStarUsed, m.UnusedImport) + + error = checker.messages[0] + assert error.message.startswith("'from %s import *' used; unable ") + assert error.message_args == ('..', ) + + error = checker.messages[1] + assert error.message == '%r imported but unused' + assert error.message_args == ('from .. import *', ) + + def test_localImportStar(self): + """import * is only allowed at module level.""" + self.flakes(''' + def a(): + from fu import * + + ''', m.ImportStarNotPermitted) + self.flakes(''' + class a: + from fu import * + ''', m.ImportStarNotPermitted) + + checker = self.flakes(''' + class a: + from .. import * + ''', m.ImportStarNotPermitted) + error = checker.messages[0] + assert error.message == "'from %s import *' only allowed at module level" + assert error.message_args == ('..', ) + + def test_packageImport(self): + """ + If a dotted name is imported and used, no warning is reported. + """ + self.flakes(''' + import fu.bar + fu.bar + + ''') + + def test_unusedPackageImport(self): + """ + If a dotted name is imported and not used, an unused import warning is + reported. + """ + self.flakes('import fu.bar', m.UnusedImport) + + + def test_duplicateSubmoduleImport(self): + """ + If a submodule of a package is imported twice, an unused import warning + and a redefined while unused warning are reported. + """ + self.flakes(''' + import fu.bar, fu.bar + fu.bar + + ''', m.RedefinedWhileUnused) + self.flakes(''' + import fu.bar + import fu.bar + fu.bar + ''', m.RedefinedWhileUnused) + + def test_differentSubmoduleImport(self): + """ + If two different submodules of a package are imported, no duplicate + import warning is reported for the package. + """ + self.flakes(''' + import fu.bar, fu.baz + fu.bar, fu.baz + + ''') + self.flakes(''' + import fu.bar + import fu.baz + fu.bar, fu.baz + ''') + + def test_used_package_with_submodule_import(self): + """ + Usage of package marks submodule imports as used. + """ + self.flakes(''' + import fu + import fu.bar + fu.x + + ''') + + self.flakes(''' + import fu.bar + import fu + fu.x + ''') + + def test_used_package_with_submodule_import_of_alias(self): + """ + Usage of package by alias marks submodule imports as used. + """ + self.flakes(''' + import foo as f + import foo.bar + f.bar.do_something() + + ''') + + self.flakes(''' + import foo as f + import foo.bar.blah + f.bar.blah.do_something() + ''') + + def test_unused_package_with_submodule_import(self): + """ + When a package and its submodule are imported, only report once. + """ + checker = self.flakes(''' + import fu + import fu.bar + +class ClassDefinition(Definition): + pass + + + + ''', m.UnusedImport) + error = checker.messages[0] + assert error.message == '%r imported but unused' + assert error.message_args == ('fu.bar', ) + assert error.lineno == 5 if self.withDoctest else 3 + + def test_assignRHSFirst(self): + self.flakes('import fu; fu = fu') + self.flakes('import fu; fu, bar = fu') + self.flakes('import fu; [fu, bar] = fu') + self.flakes('import fu; fu += fu') + + + def test_tryingMultipleImports(self): + self.flakes(''' + try: + import fu + except ImportError: + import bar as fu + fu + + ''') + + def test_nonGlobalDoesNotRedefine(self): + self.flakes(''' + import fu + def a(): + fu = 3 + return fu + fu + + ''') + + def test_functionsRunLater(self): + self.flakes(''' + def a(): + fu + import fu + + ''') + + def test_functionNamesAreBoundNow(self): + self.flakes(''' + import fu + def fu(): + fu + fu + + ''', m.RedefinedWhileUnused) + + def test_ignoreNonImportRedefinitions(self): + self.flakes('a = 1; a = 2') + + + @skip("todo") + def test_importingForImportError(self): + self.flakes(''' + try: + import fu + except ImportError: + pass + + ''') + + def test_importedInClass(self): + """Imports in class scope can be used through self.""" + self.flakes(''' + class c: + import i + def __init__(self): + self.i + + ''') + + def test_importUsedInMethodDefinition(self): + """ + Method named 'foo' with default args referring to module named 'foo'. + """ + self.flakes(''' + import foo + + class Thing(object): + def foo(self, parser=foo.parse_foo): + pass + + ''') + + def test_futureImport(self): + """__future__ is special.""" + self.flakes('from __future__ import division') + self.flakes(''' + "docstring is allowed before future import" + from __future__ import division + +class ExportBinding(Binding): + """ + A binding created by an C{__all__} assignment. If the names in the list + can be determined statically, they will be treated as names for export and + additional checking applied to them. + + The only recognized C{__all__} assignment via list/tuple concatenation is in the + following format: + + __all__ = ['a'] + ['b'] + ['c'] + + Names which are imported and not otherwise used but appear in the value of + C{__all__} will not have an unused import warning reported for them. + """ + + @others + + ''') + + def test_futureImportFirst(self): + """ + __future__ imports must come before anything else. + """ + self.flakes(''' + x = 5 + from __future__ import division + + ''', m.LateFutureImport) + self.flakes(''' + from foo import bar + from __future__ import division + bar + ''', m.LateFutureImport) + + def test_futureImportUsed(self): + """__future__ is special, but names are injected in the namespace.""" + self.flakes(''' + from __future__ import division + from __future__ import print_function + + assert print_function is not division + + ''') + + def test_futureImportUndefined(self): + """Importing undefined names from __future__ fails.""" + self.flakes(''' + from __future__ import print_statement + + ''', m.FutureFeatureNotDefined) + + def test_futureImportStar(self): + """Importing '*' from __future__ fails.""" + self.flakes(''' + from __future__ import * + + ''', m.FutureFeatureNotDefined) + + +class TestSpecialAll(TestCase): + """ + Tests for suppression of unused import warnings by C{__all__}. + """ + @others + + ''', m.UnusedImport, m.UnusedVariable) + + def test_ignoredInClass(self): + """ + An C{__all__} definition in a class does not suppress unused import warnings. + """ + self.flakes(''' + import bar + class foo: + __all__ = ["bar"] + + ''', m.UnusedImport) + + def test_ignored_when_not_directly_assigned(self): + self.flakes(''' + import bar + (__all__,) = ("foo",) + + ''', m.UnusedImport) + + def test_warningSuppressed(self): + """ + If a name is imported and unused but is named in C{__all__}, no warning + is reported. + """ + self.flakes(''' + import foo + __all__ = ["foo"] + + ''') + self.flakes(''' + import foo + __all__ = ("foo",) + ''') + + def test_augmentedAssignment(self): + """ + The C{__all__} variable is defined incrementally. + """ + self.flakes(''' + import a + import c + __all__ = ['a'] + __all__ += ['b'] + if 1 < 3: + __all__ += ['c', 'd'] + + ''', m.UndefinedExport, m.UndefinedExport) + + def test_list_concatenation_assignment(self): + """ + The C{__all__} variable is defined through list concatenation. + """ + self.flakes(''' + import sys + __all__ = ['a'] + ['b'] + ['c'] + +class Scope(dict): + importStarred = False # set to True when import * is found + + def __repr__(self): + scope_cls = self.__class__.__name__ + return f'<{scope_cls} at 0x{id(self):x} {dict.__repr__(self)}>' + + + + ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport) + + def test_tuple_concatenation_assignment(self): + """ + The C{__all__} variable is defined through tuple concatenation. + """ + self.flakes(''' + import sys + __all__ = ('a',) + ('b',) + ('c',) + + ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport) + + def test_all_with_attributes(self): + self.flakes(''' + from foo import bar + __all__ = [bar.__name__] + + ''') + + def test_all_with_names(self): + # not actually valid, but shouldn't produce a crash + self.flakes(''' + from foo import bar + __all__ = [bar] + + ''') + + def test_all_with_attributes_added(self): + self.flakes(''' + from foo import bar + from bar import baz + __all__ = [bar.__name__] + [baz.__name__] + + ''') + + def test_all_mixed_attributes_and_strings(self): + self.flakes(''' + from foo import bar + from foo import baz + __all__ = ['bar', baz.__name__] + + ''') + + def test_unboundExported(self): + """ + If C{__all__} includes a name which is not bound, a warning is emitted. + """ + self.flakes(''' + __all__ = ["foo"] + + ''', m.UndefinedExport) + + # Skip this in __init__.py though, since the rules there are a little + # different. + for filename in ["foo/__init__.py", "__init__.py"]: + self.flakes(''' + __all__ = ["foo"] + ''', filename=filename) + + def test_importStarExported(self): + """ + Report undefined if import * is used + """ + self.flakes(''' + from math import * + __all__ = ['sin', 'cos'] + csc(1) + + ''', m.ImportStarUsed, m.ImportStarUsage, m.ImportStarUsage, m.ImportStarUsage) + + def test_importStarNotExported(self): + """Report unused import when not needed to satisfy __all__.""" + self.flakes(''' + from foolib import * + a = 1 + __all__ = ['a'] + + ''', m.ImportStarUsed, m.UnusedImport) + + def test_usedInGenExp(self): + """ + Using a global in a generator expression results in no warnings. + """ + self.flakes('import fu; (fu for _ in range(1))') + self.flakes('import fu; (1 for _ in range(1) if fu)') + + + def test_redefinedByGenExp(self): + """ + Re-using a global name as the loop variable for a generator + expression results in a redefinition warning. + """ + self.flakes('import fu; (1 for fu in range(1))', + m.RedefinedWhileUnused, m.UnusedImport) + + +def get_long_description(): + descr = [] + for fname in ('README.rst',): + with open(fname) as f: + descr.append(f.read()) + return '\n\n'.join(descr) + + + +class ClassScope(Scope): + pass + + + + def test_usedAsDecorator(self): + """ + Using a global name in a decorator statement results in no warnings, + but using an undefined name in a decorator statement results in an + undefined name warning. + """ + self.flakes(''' + from interior import decorate + @decorate + def f(): + return "hello" + + ''') + + self.flakes(''' + from interior import decorate + @decorate('value') + def f(): + return "hello" + ''') + + self.flakes(''' + @decorate + def f(): + return "hello" + ''', m.UndefinedName) + + def test_usedAsClassDecorator(self): + """ + Using an imported name as a class decorator results in no warnings, + but using an undefined name as a class decorator results in an + undefined name warning. + """ + self.flakes(''' + from interior import decorate + @decorate + class foo: + pass + +def test_import_basic(self): + binding = Importation('a', None, 'a') + assert binding.source_statement == 'import a' + assert str(binding) == 'a' + + +def test_import_as(self): + binding = Importation('c', None, 'a') + assert binding.source_statement == 'import a as c' + assert str(binding) == 'a as c' + + +def test_import_submodule(self): + binding = SubmoduleImportation('a.b', None) + assert binding.source_statement == 'import a.b' + assert str(binding) == 'a.b' + + +def test_import_submodule_as(self): + # A submodule import with an as clause is not a SubmoduleImportation + binding = Importation('c', None, 'a.b') + assert binding.source_statement == 'import a.b as c' + assert str(binding) == 'a.b as c' + + +def test_import_submodule_as_source_name(self): + binding = Importation('a', None, 'a.b') + assert binding.source_statement == 'import a.b as a' + assert str(binding) == 'a.b as a' + + +def test_importfrom_relative(self): + binding = ImportationFrom('a', None, '.', 'a') + assert binding.source_statement == 'from . import a' + assert str(binding) == '.a' + + +def test_importfrom_relative_parent(self): + binding = ImportationFrom('a', None, '..', 'a') + assert binding.source_statement == 'from .. import a' + assert str(binding) == '..a' + + +def test_importfrom_relative_with_module(self): + binding = ImportationFrom('b', None, '..a', 'b') + assert binding.source_statement == 'from ..a import b' + assert str(binding) == '..a.b' + + +class FunctionScope(Scope): + """ + I represent a name scope for a function. + + @ivar globals: Names declared 'global' in this function. + """ + @others + +def test_importfrom_relative_with_module_as(self): + binding = ImportationFrom('c', None, '..a', 'b') + assert binding.source_statement == 'from ..a import b as c' + assert str(binding) == '..a.b as c' + + +def test_importfrom_member(self): + binding = ImportationFrom('b', None, 'a', 'b') + assert binding.source_statement == 'from a import b' + assert str(binding) == 'a.b' + + +def test_importfrom_submodule_member(self): + binding = ImportationFrom('c', None, 'a.b', 'c') + assert binding.source_statement == 'from a.b import c' + assert str(binding) == 'a.b.c' + + +def test_importfrom_member_as(self): + binding = ImportationFrom('c', None, 'a', 'b') + assert binding.source_statement == 'from a import b as c' + assert str(binding) == 'a.b as c' + + +def test_importfrom_submodule_member_as(self): + binding = ImportationFrom('d', None, 'a.b', 'c') + assert binding.source_statement == 'from a.b import c as d' + assert str(binding) == 'a.b.c as d' + + +def test_importfrom_star(self): + binding = StarImportation('a.b', None) + assert binding.source_statement == 'from a.b import *' + assert str(binding) == 'a.b.*' + + +def test_importfrom_star_relative(self): + binding = StarImportation('.b', None) + assert binding.source_statement == 'from .b import *' + assert str(binding) == '.b.*' + + +def test_importfrom_future(self): + binding = FutureImportation('print_function', None, None) + assert binding.source_statement == 'from __future__ import print_function' + assert str(binding) == '__future__.print_function' + + +def test_unusedImport_underscore(self): + """ + The magic underscore var should be reported as unused when used as an + import alias. + """ + self.flakes('import fu as _', m.UnusedImport) + + + +def test_unusedImport(self): + self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport) + self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport) + + +class TypeScope(Scope): + pass + + + +def test_unusedImport_relative(self): + self.flakes('from . import fu', m.UnusedImport) + self.flakes('from . import fu as baz', m.UnusedImport) + self.flakes('from .. import fu', m.UnusedImport) + self.flakes('from ... import fu', m.UnusedImport) + self.flakes('from .. import fu as baz', m.UnusedImport) + self.flakes('from .bar import fu', m.UnusedImport) + self.flakes('from ..bar import fu', m.UnusedImport) + self.flakes('from ...bar import fu', m.UnusedImport) + self.flakes('from ...bar import fu as baz', m.UnusedImport) + + checker = self.flakes('from . import fu', m.UnusedImport) + + error = checker.messages[0] + assert error.message == '%r imported but unused' + assert error.message_args == ('.fu', ) + + checker = self.flakes('from . import fu as baz', m.UnusedImport) + + error = checker.messages[0] + assert error.message == '%r imported but unused' + assert error.message_args == ('.fu as baz', ) + + +def test_aliasedImport(self): + self.flakes('import fu as FU, bar as FU', + m.RedefinedWhileUnused, m.UnusedImport) + self.flakes('from moo import fu as FU, bar as FU', + m.RedefinedWhileUnused, m.UnusedImport) + + +def test_aliasedImportShadowModule(self): + """Imported aliases can shadow the source of the import.""" + self.flakes('from moo import fu as moo; moo') + self.flakes('import fu as fu; fu') + self.flakes('import fu.bar as fu; fu') + + +def test_usedImport(self): + self.flakes('import fu; print(fu)') + self.flakes('from baz import fu; print(fu)') + self.flakes('import fu; del fu') + + +def test_usedImport_relative(self): + self.flakes('from . import fu; assert fu') + self.flakes('from .bar import fu; assert fu') + self.flakes('from .. import fu; assert fu') + self.flakes('from ..bar import fu as baz; assert baz') + + +def test_redefinedWhileUnused(self): + self.flakes('import fu; fu = 3', m.RedefinedWhileUnused) + self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused) + self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused) + + +def test_redefinedIf(self): + """ + Test that importing a module twice within an if + block does raise a warning. + """ + self.flakes(''' + i = 2 + if i==1: + import os + import os + +def test_ignoredInFunction(self): + """ + An C{__all__} definition does not suppress unused import warnings in a + function scope. + """ + self.flakes(''' + def foo(): + import bar + __all__ = ["bar"] + +@path pyflakes/test +from pyflakes.messages import IsLiteral +from pyflakes.test.harness import TestCase + + +@others + ''') +@language python +@tabwidth -4 + +class Test(TestCase): + @others + +class GeneratorScope(Scope): + pass + + + + """, IsLiteral) + + def test_is_bytes(self): + self.flakes(""" + x = b'foo' + if x is b'foo': + pass + + """, IsLiteral) + + def test_is_unicode(self): + self.flakes(""" + x = u'foo' + if x is u'foo': + pass + + """, IsLiteral) + + def test_is_int(self): + self.flakes(""" + x = 10 + if x is 10: + pass + + """, IsLiteral) + + def test_is_true(self): + self.flakes(""" + x = True + if x is True: + pass + + """) + + def test_is_false(self): + self.flakes(""" + x = False + if x is False: + pass + + """) + + def test_is_not_str(self): + self.flakes(""" + x = 'foo' + if x is not 'foo': + pass + + """, IsLiteral) + + def test_is_not_bytes(self): + self.flakes(""" + x = b'foo' + if x is not b'foo': + pass + + """, IsLiteral) + + def test_is_not_unicode(self): + self.flakes(""" + x = u'foo' + if x is not u'foo': + pass + + """, IsLiteral) + + def test_is_not_int(self): + self.flakes(""" + x = 10 + if x is not 10: + pass + + """, IsLiteral) + + def test_is_not_true(self): + self.flakes(""" + x = True + if x is not True: + pass + +class ModuleScope(Scope): + """Scope for a module.""" + _futures_allowed = True + _annotations_future_enabled = False + + + + """) + + def test_is_not_false(self): + self.flakes(""" + x = False + if x is not False: + pass + + """) + + def test_left_is_str(self): + self.flakes(""" + x = 'foo' + if 'foo' is x: + pass + + """, IsLiteral) + + def test_left_is_bytes(self): + self.flakes(""" + x = b'foo' + if b'foo' is x: + pass + + """, IsLiteral) + + def test_left_is_unicode(self): + self.flakes(""" + x = u'foo' + if u'foo' is x: + pass + + """, IsLiteral) + + def test_left_is_int(self): + self.flakes(""" + x = 10 + if 10 is x: + pass + + """, IsLiteral) + + def test_left_is_true(self): + self.flakes(""" + x = True + if True is x: + pass + + """) + + def test_left_is_false(self): + self.flakes(""" + x = False + if False is x: + pass + + """) + + def test_left_is_not_str(self): + self.flakes(""" + x = 'foo' + if 'foo' is not x: + pass + + """, IsLiteral) + + def test_left_is_not_bytes(self): + self.flakes(""" + x = b'foo' + if b'foo' is not x: + pass + + """, IsLiteral) + + def test_left_is_not_unicode(self): + self.flakes(""" + x = u'foo' + if u'foo' is not x: + pass + +class DoctestScope(ModuleScope): + """Scope for a doctest.""" + + + + """, IsLiteral) + + def test_left_is_not_int(self): + self.flakes(""" + x = 10 + if 10 is not x: + pass + + """, IsLiteral) + + def test_left_is_not_true(self): + self.flakes(""" + x = True + if True is not x: + pass + + """) + + def test_left_is_not_false(self): + self.flakes(""" + x = False + if False is not x: + pass + + """) + + def test_chained_operators_is_true(self): + self.flakes(""" + x = 5 + if x is True < 4: + pass + + """) + + def test_chained_operators_is_str(self): + self.flakes(""" + x = 5 + if x is 'foo' < 4: + pass + + """, IsLiteral) + + def test_chained_operators_is_true_end(self): + self.flakes(""" + x = 5 + if 4 < x is True: + pass + + """) + + def test_chained_operators_is_str_end(self): + self.flakes(""" + x = 5 + if 4 < x is 'foo': + pass + + """, IsLiteral) + + def test_is_tuple_constant(self): + self.flakes('''\ + x = 5 + if x is (): + pass + + ''', IsLiteral) + + def test_is_tuple_constant_containing_constants(self): + self.flakes('''\ + x = 5 + if x is (1, '2', True, (1.5, ())): + pass + + ''', IsLiteral) + + def test_is_tuple_containing_variables_ok(self): + # a bit nonsensical, but does not trigger a SyntaxWarning + self.flakes('''\ + x = 5 + if x is (x,): + pass + +class DetectClassScopedMagic: + names = dir() + + + +def test_is_str(self): + self.flakes(""" + x = 'foo' + if x is 'foo': + pass + +@path pyflakes/test +from sys import version_info + +from pyflakes.test.harness import TestCase, skipIf + + +@skipIf(version_info < (3, 10), "Python >= 3.10 only") +@others + ''') +@language python +@tabwidth -4 + +class TestMatch(TestCase): + @others + + ''') + self.flakes(''' + def f(): + x = [1, 2, 3] + match x: + case [1, y, 3]: + print(f'matched {y}') + ''') + self.flakes(''' + def f(): + x = {'foo': 1} + match x: + case {'foo': y}: + print(f'matched {y}') + ''') + + def test_match_pattern_matched_class(self): + self.flakes(''' + from a import B + + match 1: + case B(x=1) as y: + print(f'matched {y}') + + ''') + self.flakes(''' + from a import B + + match 1: + case B(a, x=z) as y: + print(f'matched {y} {a} {z}') + ''') + + def test_match_placeholder(self): + self.flakes(''' + def f(): + match 1: + case _: + print('catchall!') + + ''') + + def test_match_singleton(self): + self.flakes(''' + match 1: + case True: + print('true') + + ''') + + def test_match_or_pattern(self): + self.flakes(''' + match 1: + case 1 | 2: + print('one or two') + + ''') + + def test_match_star(self): + self.flakes(''' + x = [1, 2, 3] + match x: + case [1, *y]: + print(f'captured: {y}') + + ''') + + def test_match_double_star(self): + self.flakes(''' + x = {'foo': 'bar', 'baz': 'womp'} + match x: + case {'foo': k1, **rest}: + print(f'{k1=} {rest=}') + + ''') + + def test_defined_in_different_branches(self): + self.flakes(''' + def f(x): + match x: + case 1: + def y(): pass + case _: + def y(): print(1) + return y + +def getNodeName(node): + # Returns node.id, or node.name, or None + if hasattr(node, 'id'): # One of the many nodes with an id + return node.id + if hasattr(node, 'name'): # an ExceptHandler node + return node.name + if hasattr(node, 'rest'): # a MatchMapping node + return node.rest + + + +def test_match_bindings(self): + self.flakes(''' + def f(): + x = 1 + match x: + case 1 as y: + print(f'matched as {y}') + +@path pyflakes/test +""" +Tests for various Pyflakes behavior. +""" + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skip, skipIf + + +@others + ''') +@language python +@tabwidth -4 + +class Test(TestCase): + + @others + + ''', m.UndefinedLocal, m.UnusedVariable) + + def test_redefinedInGenerator(self): + """ + Test that reusing a variable in a generator does not raise + a warning. + """ + self.flakes(''' + a = 1 + (1 for a, b in [(1, 2)]) + + ''') + self.flakes(''' + class A: + a = 1 + list(1 for a, b in [(1, 2)]) + ''') + self.flakes(''' + def f(): + a = 1 + (1 for a, b in [(1, 2)]) + ''', m.UnusedVariable) + self.flakes(''' + (1 for a, b in [(1, 2)]) + (1 for a, b in [(1, 2)]) + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + (1 for a, b in [(1, 2)]) + ''') + + def test_redefinedInSetComprehension(self): + """ + Test that reusing a variable in a set comprehension does not raise + a warning. + """ + self.flakes(''' + a = 1 + {1 for a, b in [(1, 2)]} + + ''') + self.flakes(''' + class A: + a = 1 + {1 for a, b in [(1, 2)]} + ''') + self.flakes(''' + def f(): + a = 1 + {1 for a, b in [(1, 2)]} + ''', m.UnusedVariable) + self.flakes(''' + {1 for a, b in [(1, 2)]} + {1 for a, b in [(1, 2)]} + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + {1 for a, b in [(1, 2)]} + ''') + + def test_redefinedInDictComprehension(self): + """ + Test that reusing a variable in a dict comprehension does not raise + a warning. + """ + self.flakes(''' + a = 1 + {1: 42 for a, b in [(1, 2)]} + + ''') + self.flakes(''' + class A: + a = 1 + {1: 42 for a, b in [(1, 2)]} + ''') + self.flakes(''' + def f(): + a = 1 + {1: 42 for a, b in [(1, 2)]} + ''', m.UnusedVariable) + self.flakes(''' + {1: 42 for a, b in [(1, 2)]} + {1: 42 for a, b in [(1, 2)]} + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + {1: 42 for a, b in [(1, 2)]} + ''') + + def test_redefinedFunction(self): + """ + Test that shadowing a function definition with another one raises a + warning. + """ + self.flakes(''' + def a(): pass + def a(): pass + + ''', m.RedefinedWhileUnused) + + def test_redefined_function_shadows_variable(self): + self.flakes(''' + x = 1 + def x(): pass + + ''', m.RedefinedWhileUnused) + + def test_redefinedUnderscoreFunction(self): + """ + Test that shadowing a function definition named with underscore doesn't + raise anything. + """ + self.flakes(''' + def _(): pass + def _(): pass + + ''') + + def test_redefinedUnderscoreImportation(self): + """ + Test that shadowing an underscore importation raises a warning. + """ + self.flakes(''' + from .i18n import _ + def _(): pass + +def _is_typing_helper(node, is_name_match_fn, scope_stack): + """ + Internal helper to determine whether or not something is a member of a + typing module. This is used as part of working out whether we are within a + type annotation context. + + Note: you probably don't want to use this function directly. Instead see the + utils below which wrap it (`_is_typing` and `_is_any_typing_member`). + """ + + def _bare_name_is_attr(name): + for scope in reversed(scope_stack): + if name in scope: + return ( + isinstance(scope[name], ImportationFrom) and + scope[name].module in TYPING_MODULES and + is_name_match_fn(scope[name].real_name) + ) + + return False + + def _module_scope_is_typing(name): + for scope in reversed(scope_stack): + if name in scope: + return ( + isinstance(scope[name], Importation) and + scope[name].fullName in TYPING_MODULES + ) + + return False + + return ( + ( + isinstance(node, ast.Name) and + _bare_name_is_attr(node.id) + ) or ( + isinstance(node, ast.Attribute) and + isinstance(node.value, ast.Name) and + _module_scope_is_typing(node.value.id) and + is_name_match_fn(node.attr) + ) + ) + + + + ''', m.RedefinedWhileUnused) + + def test_redefinedClassFunction(self): + """ + Test that shadowing a function definition in a class suite with another + one raises a warning. + """ + self.flakes(''' + class A: + def a(): pass + def a(): pass + + ''', m.RedefinedWhileUnused) + + def test_redefinedIfElseFunction(self): + """ + Test that shadowing a function definition twice in an if + and else block does not raise a warning. + """ + self.flakes(''' + if True: + def a(): pass + else: + def a(): pass + + ''') + + def test_redefinedIfFunction(self): + """ + Test that shadowing a function definition within an if block + raises a warning. + """ + self.flakes(''' + if True: + def a(): pass + def a(): pass + + ''', m.RedefinedWhileUnused) + + def test_redefinedTryExceptFunction(self): + """ + Test that shadowing a function definition twice in try + and except block does not raise a warning. + """ + self.flakes(''' + try: + def a(): pass + except: + def a(): pass + + ''') + + def test_redefinedTryFunction(self): + """ + Test that shadowing a function definition within a try block + raises a warning. + """ + self.flakes(''' + try: + def a(): pass + def a(): pass + except: + pass + + ''', m.RedefinedWhileUnused) + + def test_redefinedIfElseInListComp(self): + """ + Test that shadowing a variable in a list comprehension in + an if and else block does not raise a warning. + """ + self.flakes(''' + if False: + a = 1 + else: + [a for a in '12'] + + ''') + + def test_functionDecorator(self): + """ + Test that shadowing a function definition with a decorated version of + that function does not raise a warning. + """ + self.flakes(''' + from somewhere import somedecorator + + def a(): pass + a = somedecorator(a) + + ''') + + def test_classFunctionDecorator(self): + """ + Test that shadowing a function definition in a class suite with a + decorated version of that function does not raise a warning. + """ + self.flakes(''' + class A: + def a(): pass + a = classmethod(a) + + ''') + + def test_modernProperty(self): + self.flakes(""" + class A: + @property + def t(self): + pass + @t.setter + def t(self, value): + pass + @t.deleter + def t(self): + pass + + """) + + def test_unaryPlus(self): + """Don't die on unary +.""" + self.flakes('+1') + + +def _is_typing(node, typing_attr, scope_stack): + """ + Determine whether `node` represents the member of a typing module specified + by `typing_attr`. + + This is used as part of working out whether we are within a type annotation + context. + """ + return _is_typing_helper(node, lambda x: x == typing_attr, scope_stack) + + + + def test_undefinedBaseClass(self): + """ + If a name in the base list of a class definition is undefined, a + warning is emitted. + """ + self.flakes(''' + class foo(foo): + pass + + ''', m.UndefinedName) + + def test_classNameUndefinedInClassBody(self): + """ + If a class name is used in the body of that class's definition and + the name is not already defined, a warning is emitted. + """ + self.flakes(''' + class foo: + foo + + ''', m.UndefinedName) + + def test_classNameDefinedPreviously(self): + """ + If a class name is used in the body of that class's definition and + the name was previously defined in some other way, no warning is + emitted. + """ + self.flakes(''' + foo = None + class foo: + foo + + ''') + + def test_classRedefinition(self): + """ + If a class is defined twice in the same module, a warning is emitted. + """ + self.flakes(''' + class Foo: + pass + class Foo: + pass + + ''', m.RedefinedWhileUnused) + + def test_functionRedefinedAsClass(self): + """ + If a function is redefined as a class, a warning is emitted. + """ + self.flakes(''' + def Foo(): + pass + class Foo: + pass + + ''', m.RedefinedWhileUnused) + + def test_classRedefinedAsFunction(self): + """ + If a class is redefined as a function, a warning is emitted. + """ + self.flakes(''' + class Foo: + pass + def Foo(): + pass + + ''', m.RedefinedWhileUnused) + + def test_classWithReturn(self): + """ + If a return is used inside a class, a warning is emitted. + """ + self.flakes(''' + class Foo(object): + return + + ''', m.ReturnOutsideFunction) + + def test_moduleWithReturn(self): + """ + If a return is used at the module level, a warning is emitted. + """ + self.flakes(''' + return + + ''', m.ReturnOutsideFunction) + + def test_classWithYield(self): + """ + If a yield is used inside a class, a warning is emitted. + """ + self.flakes(''' + class Foo(object): + yield + + ''', m.YieldOutsideFunction) + + def test_moduleWithYield(self): + """ + If a yield is used at the module level, a warning is emitted. + """ + self.flakes(''' + yield + +def _is_any_typing_member(node, scope_stack): + """ + Determine whether `node` represents any member of a typing module. + + This is used as part of working out whether we are within a type annotation + context. + """ + return _is_typing_helper(node, lambda x: True, scope_stack) + + + + ''', m.YieldOutsideFunction) + + def test_classWithYieldFrom(self): + """ + If a yield from is used inside a class, a warning is emitted. + """ + self.flakes(''' + class Foo(object): + yield from range(10) + + ''', m.YieldOutsideFunction) + + def test_moduleWithYieldFrom(self): + """ + If a yield from is used at the module level, a warning is emitted. + """ + self.flakes(''' + yield from range(10) + + ''', m.YieldOutsideFunction) + + def test_continueOutsideLoop(self): + self.flakes(''' + continue + + ''', m.ContinueOutsideLoop) + + self.flakes(''' + def f(): + continue + ''', m.ContinueOutsideLoop) + + self.flakes(''' + while True: + pass + else: + continue + ''', m.ContinueOutsideLoop) + + self.flakes(''' + while True: + pass + else: + if 1: + if 2: + continue + ''', m.ContinueOutsideLoop) + + self.flakes(''' + while True: + def f(): + continue + ''', m.ContinueOutsideLoop) + + self.flakes(''' + while True: + class A: + continue + ''', m.ContinueOutsideLoop) + + def test_continueInsideLoop(self): + self.flakes(''' + while True: + continue + + ''') + + self.flakes(''' + for i in range(10): + continue + ''') + + self.flakes(''' + while True: + if 1: + continue + ''') + + self.flakes(''' + for i in range(10): + if 1: + continue + ''') + + self.flakes(''' + while True: + while True: + pass + else: + continue + else: + pass + ''') + + self.flakes(''' + while True: + try: + pass + finally: + while True: + continue + ''') + + def test_breakOutsideLoop(self): + self.flakes(''' + break + + ''', m.BreakOutsideLoop) + + self.flakes(''' + def f(): + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + while True: + pass + else: + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + while True: + pass + else: + if 1: + if 2: + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + while True: + def f(): + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + while True: + class A: + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + try: + pass + finally: + break + ''', m.BreakOutsideLoop) + + def test_breakInsideLoop(self): + self.flakes(''' + while True: + break + + ''') + + self.flakes(''' + for i in range(10): + break + ''') + + self.flakes(''' + while True: + if 1: + break + ''') + + self.flakes(''' + for i in range(10): + if 1: + break + ''') + + self.flakes(''' + while True: + while True: + pass + else: + break + else: + pass + ''') + + self.flakes(''' + while True: + try: + pass + finally: + while True: + break + ''') + + self.flakes(''' + while True: + try: + pass + finally: + break + ''') + + self.flakes(''' + while True: + try: + pass + finally: + if 1: + if 2: + break + ''') + + def test_defaultExceptLast(self): + """ + A default except block should be last. + + YES: + + try: + ... + except Exception: + ... + except: + ... + + NO: + + try: + ... + except: + ... + except Exception: + ... + """ + self.flakes(''' + try: + pass + except ValueError: + pass + + ''') + + self.flakes(''' + try: + pass + except ValueError: + pass + except: + pass + ''') + + self.flakes(''' + try: + pass + except: + pass + ''') + + self.flakes(''' + try: + pass + except ValueError: + pass + else: + pass + ''') + + self.flakes(''' + try: + pass + except: + pass + else: + pass + ''') + + self.flakes(''' + try: + pass + except ValueError: + pass + except: + pass + else: + pass + ''') + + def test_defaultExceptNotLast(self): + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + except ValueError: + pass + ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + else: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except: + pass + else: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + else: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + except ValueError: + pass + else: + pass + ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + except ValueError: + pass + finally: + pass + ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + else: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except: + pass + else: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + else: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + except ValueError: + pass + else: + pass + finally: + pass + ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) + + def test_starredAssignmentNoError(self): + """ + Python 3 extended iterable unpacking + """ + self.flakes(''' + a, *b = range(10) + + ''') + + self.flakes(''' + *a, b = range(10) + ''') + + self.flakes(''' + a, *b, c = range(10) + ''') + + self.flakes(''' + (a, *b) = range(10) + ''') + + self.flakes(''' + (*a, b) = range(10) + ''') + + self.flakes(''' + (a, *b, c) = range(10) + ''') + + self.flakes(''' + [a, *b] = range(10) + ''') + + self.flakes(''' + [*a, b] = range(10) + ''') + + self.flakes(''' + [a, *b, c] = range(10) + ''') + + # Taken from test_unpack_ex.py in the cPython source + s = ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \ + ", *rest = range(1<<8)" + self.flakes(s) + + s = "(" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \ + ", *rest) = range(1<<8)" + self.flakes(s) + + s = "[" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \ + ", *rest] = range(1<<8)" + self.flakes(s) + + def test_starredAssignmentErrors(self): + """ + SyntaxErrors (not encoded in the ast) surrounding Python 3 extended + iterable unpacking + """ + # Taken from test_unpack_ex.py in the cPython source + s = ", ".join("a%d" % i for i in range(1 << 8)) + \ + ", *rest = range(1<<8 + 1)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = "(" + ", ".join("a%d" % i for i in range(1 << 8)) + \ + ", *rest) = range(1<<8 + 1)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = "[" + ", ".join("a%d" % i for i in range(1 << 8)) + \ + ", *rest] = range(1<<8 + 1)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \ + ", *rest = range(1<<8 + 2)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = "(" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \ + ", *rest) = range(1<<8 + 2)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = "[" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \ + ", *rest] = range(1<<8 + 2)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + # No way we can actually test this! + # s = "*rest, " + ", ".join("a%d" % i for i in range(1<<24)) + \ + # ", *rest = range(1<<24 + 1)" + # self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + self.flakes(''' + a, *b, *c = range(10) + +def is_typing_overload(value, scope_stack): + return ( + isinstance(value.source, (ast.FunctionDef, ast.AsyncFunctionDef)) and + any( + _is_typing(dec, 'overload', scope_stack) + for dec in value.source.decorator_list + ) + ) + + + + ''', m.TwoStarredExpressions) + + self.flakes(''' + a, *b, c, *d = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + *a, *b, *c = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + (a, *b, *c) = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + (a, *b, c, *d) = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + (*a, *b, *c) = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + [a, *b, *c] = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + [a, *b, c, *d] = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + [*a, *b, *c] = range(10) + ''', m.TwoStarredExpressions) + + @skip("todo: Too hard to make this warn but other cases stay silent") + def test_doubleAssignment(self): + """ + If a variable is re-assigned to without being used, no warning is + emitted. + """ + self.flakes(''' + x = 10 + x = 20 + + ''', m.RedefinedWhileUnused) + + def test_doubleAssignmentConditionally(self): + """ + If a variable is re-assigned within a conditional, no warning is + emitted. + """ + self.flakes(''' + x = 10 + if True: + x = 20 + + ''') + + def test_doubleAssignmentWithUse(self): + """ + If a variable is re-assigned to after being used, no warning is + emitted. + """ + self.flakes(''' + x = 10 + y = x * 2 + x = 20 + + ''') + + def test_comparison(self): + """ + If a defined name is used on either side of any of the six comparison + operators, no warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x < y + x <= y + x == y + x != y + x >= y + x > y + + ''') + + def test_identity(self): + """ + If a defined name is used on either side of an identity test, no + warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x is y + x is not y + + ''') + + def test_containment(self): + """ + If a defined name is used on either side of a containment test, no + warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x in y + x not in y + + ''') + + def test_loopControl(self): + """ + break and continue statements are supported. + """ + self.flakes(''' + for x in [1, 2]: + break + + ''') + self.flakes(''' + for x in [1, 2]: + continue + ''') + + def test_ellipsis(self): + """ + Ellipsis in a slice is supported. + """ + self.flakes(''' + [1, 2][...] + + ''') + + def test_extendedSlice(self): + """ + Extended slices are supported. + """ + self.flakes(''' + x = 3 + [1, 2][x,:] + + ''') + + def test_varAugmentedAssignment(self): + """ + Augmented assignment of a variable is supported. + We don't care about var refs. + """ + self.flakes(''' + foo = 0 + foo += 1 + +class AnnotationState: + NONE = 0 + STRING = 1 + BARE = 2 + + + + ''') + + def test_attrAugmentedAssignment(self): + """ + Augmented assignment of attributes is supported. + We don't care about attr refs. + """ + self.flakes(''' + foo = None + foo.bar += foo.baz + + ''') + + def test_globalDeclaredInDifferentScope(self): + """ + A 'global' can be declared in one scope and reused in another. + """ + self.flakes(''' + def f(): global foo + def g(): foo = 'anything'; foo.is_used() + + ''') + + def test_function_arguments(self): + """ + Test to traverse ARG and ARGUMENT handler + """ + self.flakes(''' + def foo(a, b): + pass + + ''') + + self.flakes(''' + def foo(a, b, c=0): + pass + ''') + + self.flakes(''' + def foo(a, b, c=0, *args): + pass + ''') + + self.flakes(''' + def foo(a, b, c=0, *args, **kwargs): + pass + ''') + + def test_function_arguments_python3(self): + self.flakes(''' + def foo(a, b, c=0, *args, d=0, **kwargs): + pass + + ''') + + +class TestUnusedAssignment(TestCase): + """ + Tests for warning about unused assignments. + """ + + @others + + ''', m.UnusedVariable) + + def test_unusedUnderscoreVariable(self): + """ + Don't warn when the magic "_" (underscore) variable is unused. + See issue #202. + """ + self.flakes(''' + def a(unused_param): + _ = unused_param + + ''') + + def test_unusedVariableAsLocals(self): + """ + Using locals() it is perfectly valid to have unused variables + """ + self.flakes(''' + def a(): + b = 1 + return locals() + + ''') + + def test_unusedVariableNoLocals(self): + """ + Using locals() in wrong scope should not matter + """ + self.flakes(''' + def a(): + locals() + def a(): + b = 1 + return + + ''', m.UnusedVariable) + + @skip("todo: Difficult because it doesn't apply in the context of a loop") + def test_unusedReassignedVariable(self): + """ + Shadowing a used variable can still raise an UnusedVariable warning. + """ + self.flakes(''' + def a(): + b = 1 + b.foo() + b = 2 + + ''', m.UnusedVariable) + + def test_variableUsedInLoop(self): + """ + Shadowing a used variable cannot raise an UnusedVariable warning in the + context of a loop. + """ + self.flakes(''' + def a(): + b = True + while b: + b = False + +def in_annotation(func): + @functools.wraps(func) + def in_annotation_func(self, *args, **kwargs): + with self._enter_annotation(): + return func(self, *args, **kwargs) + return in_annotation_func + + + + ''') + + def test_assignToGlobal(self): + """ + Assigning to a global and then not using that global is perfectly + acceptable. Do not mistake it for an unused local variable. + """ + self.flakes(''' + b = 0 + def a(): + global b + b = 1 + + ''') + + def test_assignToNonlocal(self): + """ + Assigning to a nonlocal and then not using that binding is perfectly + acceptable. Do not mistake it for an unused local variable. + """ + self.flakes(''' + b = b'0' + def a(): + nonlocal b + b = b'1' + + ''') + + def test_assignToMember(self): + """ + Assigning to a member of another object and then not using that member + variable is perfectly acceptable. Do not mistake it for an unused + local variable. + """ + # XXX: Adding this test didn't generate a failure. Maybe not + # necessary? + self.flakes(''' + class b: + pass + def a(): + b.foo = 1 + + ''') + + def test_assignInForLoop(self): + """ + Don't warn when a variable in a for loop is assigned to but not used. + """ + self.flakes(''' + def f(): + for i in range(10): + pass + + ''') + + def test_assignInListComprehension(self): + """ + Don't warn when a variable in a list comprehension is + assigned to but not used. + """ + self.flakes(''' + def f(): + [None for i in range(10)] + + ''') + + def test_generatorExpression(self): + """ + Don't warn when a variable in a generator expression is + assigned to but not used. + """ + self.flakes(''' + def f(): + (None for i in range(10)) + + ''') + + def test_assignmentInsideLoop(self): + """ + Don't warn when a variable assignment occurs lexically after its use. + """ + self.flakes(''' + def f(): + x = None + for i in range(10): + if i > 2: + return x + x = i * 2 + + ''') + + def test_tupleUnpacking(self): + """ + Don't warn when a variable included in tuple unpacking is unused. It's + very common for variables in a tuple unpacking assignment to be unused + in good Python code, so warning will only create false positives. + """ + self.flakes(''' + def f(tup): + (x, y) = tup + + ''') + self.flakes(''' + def f(): + (x, y) = 1, 2 + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + (x, y) = coords = 1, 2 + if x > 1: + print(coords) + ''') + self.flakes(''' + def f(): + (x, y) = coords = 1, 2 + ''', m.UnusedVariable) + self.flakes(''' + def f(): + coords = (x, y) = 1, 2 + ''', m.UnusedVariable) + + def test_listUnpacking(self): + """ + Don't warn when a variable included in list unpacking is unused. + """ + self.flakes(''' + def f(tup): + [x, y] = tup + + ''') + self.flakes(''' + def f(): + [x, y] = [1, 2] + ''', m.UnusedVariable, m.UnusedVariable) + + def test_closedOver(self): + """ + Don't warn when the assignment is used in an inner function. + """ + self.flakes(''' + def barMaker(): + foo = 5 + def bar(): + return foo + return bar + +def in_string_annotation(func): + @functools.wraps(func) + def in_annotation_func(self, *args, **kwargs): + with self._enter_annotation(AnnotationState.STRING): + return func(self, *args, **kwargs) + return in_annotation_func + + + + ''') + + def test_doubleClosedOver(self): + """ + Don't warn when the assignment is used in an inner function, even if + that inner function itself is in an inner function. + """ + self.flakes(''' + def barMaker(): + foo = 5 + def bar(): + def baz(): + return foo + return bar + + ''') + + def test_tracebackhideSpecialVariable(self): + """ + Do not warn about unused local variable __tracebackhide__, which is + a special variable for py.test. + """ + self.flakes(""" + def helper(): + __tracebackhide__ = True + + """) + + def test_debuggerskipSpecialVariable(self): + """ + Do not warn about unused local variable __debuggerskip__, which is + a special variable for IPython. + """ + self.flakes(""" + def helper(): + __debuggerskip__ = True + + """) + + def test_ifexp(self): + """ + Test C{foo if bar else baz} statements. + """ + self.flakes("a = 'moo' if True else 'oink'") + self.flakes("a = foo if True else 'oink'", m.UndefinedName) + self.flakes("a = 'moo' if True else bar", m.UndefinedName) + + + def test_if_tuple(self): + """ + Test C{if (foo,)} conditions. + """ + self.flakes("""if (): pass""") + self.flakes(""" + if ( + True + ): + pass + + """) + self.flakes(""" + if ( + True, + ): + pass + """, m.IfTuple) + self.flakes(""" + x = 1 if ( + True, + ) else 2 + """, m.IfTuple) + + def test_withStatementNoNames(self): + """ + No warnings are emitted for using inside or after a nameless C{with} + statement a name defined beforehand. + """ + self.flakes(''' + bar = None + with open("foo"): + bar + bar + + ''') + + def test_withStatementSingleName(self): + """ + No warnings are emitted for using a name defined by a C{with} statement + within the suite or afterwards. + """ + self.flakes(''' + with open('foo') as bar: + bar + bar + + ''') + + def test_withStatementAttributeName(self): + """ + No warnings are emitted for using an attribute as the target of a + C{with} statement. + """ + self.flakes(''' + import foo + with open('foo') as foo.bar: + pass + + ''') + + def test_withStatementSubscript(self): + """ + No warnings are emitted for using a subscript as the target of a + C{with} statement. + """ + self.flakes(''' + import foo + with open('foo') as foo[0]: + pass + + ''') + + def test_withStatementSubscriptUndefined(self): + """ + An undefined name warning is emitted if the subscript used as the + target of a C{with} statement is not defined. + """ + self.flakes(''' + import foo + with open('foo') as foo[bar]: + pass + +class Checker: + """I check the cleanliness and sanity of Python code.""" + + << Checker: class data >> + + @others + + ''', m.UndefinedName) + + def test_withStatementTupleNames(self): + """ + No warnings are emitted for using any of the tuple of names defined by + a C{with} statement within the suite or afterwards. + """ + self.flakes(''' + with open('foo') as (bar, baz): + bar, baz + bar, baz + + ''') + + def test_withStatementListNames(self): + """ + No warnings are emitted for using any of the list of names defined by a + C{with} statement within the suite or afterwards. + """ + self.flakes(''' + with open('foo') as [bar, baz]: + bar, baz + bar, baz + + ''') + + def test_withStatementComplicatedTarget(self): + """ + If the target of a C{with} statement uses any or all of the valid forms + for that part of the grammar (See + U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}), + the names involved are checked both for definedness and any bindings + created are respected in the suite of the statement and afterwards. + """ + self.flakes(''' + c = d = e = g = h = i = None + with open('foo') as [(a, b), c[d], e.f, g[h:i]]: + a, b, c, d, e, g, h, i + a, b, c, d, e, g, h, i + + ''') + + def test_withStatementSingleNameUndefined(self): + """ + An undefined name warning is emitted if the name first defined by a + C{with} statement is used before the C{with} statement. + """ + self.flakes(''' + bar + with open('foo') as bar: + pass + + ''', m.UndefinedName) + + def test_withStatementTupleNamesUndefined(self): + """ + An undefined name warning is emitted if a name first defined by the + tuple-unpacking form of the C{with} statement is used before the + C{with} statement. + """ + self.flakes(''' + baz + with open('foo') as (bar, baz): + pass + + ''', m.UndefinedName) + + def test_withStatementSingleNameRedefined(self): + """ + A redefined name warning is emitted if a name bound by an import is + rebound by the name defined by a C{with} statement. + """ + self.flakes(''' + import bar + with open('foo') as bar: + pass + + ''', m.RedefinedWhileUnused) + + def test_withStatementTupleNamesRedefined(self): + """ + A redefined name warning is emitted if a name bound by an import is + rebound by one of the names defined by the tuple-unpacking form of a + C{with} statement. + """ + self.flakes(''' + import bar + with open('foo') as (bar, baz): + pass + + ''', m.RedefinedWhileUnused) + + def test_withStatementUndefinedInside(self): + """ + An undefined name warning is emitted if a name is used inside the + body of a C{with} statement without first being bound. + """ + self.flakes(''' + with open('foo') as bar: + baz + + ''', m.UndefinedName) + + def test_withStatementNameDefinedInBody(self): + """ + A name defined in the body of a C{with} statement can be used after + the body ends without warning. + """ + self.flakes(''' + with open('foo') as bar: + baz = 10 + baz + + ''') + + def test_withStatementUndefinedInExpression(self): + """ + An undefined name warning is emitted if a name in the I{test} + expression of a C{with} statement is undefined. + """ + self.flakes(''' + with bar as baz: + pass + + ''', m.UndefinedName) + + self.flakes(''' + with bar as bar: + pass + ''', m.UndefinedName) + + def test_dictComprehension(self): + """ + Dict comprehensions are properly handled. + """ + self.flakes(''' + a = {1: x for x in range(10)} + + ''') + + def test_setComprehensionAndLiteral(self): + """ + Set comprehensions are properly handled. + """ + self.flakes(''' + a = {1, 2, 3} + b = {x for x in range(10)} + + ''') + + def test_exceptionUsedInExcept(self): + self.flakes(''' + try: pass + except Exception as e: e + + ''') + + self.flakes(''' + def download_review(): + try: pass + except Exception as e: e + ''') + + def test_exceptionUnusedInExcept(self): + self.flakes(''' + try: pass + except Exception as e: pass + + ''', m.UnusedVariable) + + @skipIf(version_info < (3, 11), 'new in Python 3.11') + def test_exception_unused_in_except_star(self): + self.flakes(''' + try: + pass + except* OSError as e: + pass + + ''', m.UnusedVariable) + + def test_exceptionUnusedInExceptInFunction(self): + self.flakes(''' + def download_review(): + try: pass + except Exception as e: pass + + ''', m.UnusedVariable) + + def test_exceptWithoutNameInFunction(self): + """ + Don't issue false warning when an unnamed exception is used. + Previously, there would be a false warning, but only when the + try..except was in a function + """ + self.flakes(''' + import tokenize + def foo(): + try: pass + except tokenize.TokenError: pass + + ''') + + def test_exceptWithoutNameInFunctionTuple(self): + """ + Don't issue false warning when an unnamed exception is used. + This example catches a tuple of exception types. + """ + self.flakes(''' + import tokenize + def foo(): + try: pass + except (tokenize.TokenError, IndentationError): pass + + ''') + + def test_augmentedAssignmentImportedFunctionCall(self): + """ + Consider a function that is called on the right part of an + augassign operation to be used. + """ + self.flakes(''' + from foo import bar + baz = 0 + baz += bar() + + ''') + + def test_assert_without_message(self): + """An assert without a message is not an error.""" + self.flakes(''' + a = 1 + assert a + + ''') + + def test_assert_with_message(self): + """An assert with a message is not an error.""" + self.flakes(''' + a = 1 + assert a, 'x' + + ''') + + def test_assert_tuple(self): + """An assert of a non-empty tuple is always True.""" + self.flakes(''' + assert (False, 'x') + assert (False, ) + + ''', m.AssertTuple, m.AssertTuple) + + def test_assert_tuple_empty(self): + """An assert of an empty tuple is always False.""" + self.flakes(''' + assert () + + ''') + + def test_assert_static(self): + """An assert of a static value is not an error.""" + self.flakes(''' + assert True + assert 1 + + ''') + + def test_yieldFromUndefined(self): + """ + Test C{yield from} statement + """ + self.flakes(''' + def bar(): + yield from foo() + + ''', m.UndefinedName) + + def test_f_string(self): + """Test PEP 498 f-strings are treated as a usage.""" + self.flakes(''' + baz = 0 + print(f'\x7b4*baz\N{RIGHT CURLY BRACKET}') + + ''') + + def test_assign_expr(self): + """Test PEP 572 assignment expressions are treated as usage / write.""" + self.flakes(''' + from foo import y + print(x := y) + print(x) + + ''') + + def test_assign_expr_generator_scope(self): + """Test assignment expressions in generator expressions.""" + self.flakes(''' + if (any((y := x[0]) for x in [[True]])): + print(y) + + ''') + + def test_assign_expr_generator_scope_reassigns_parameter(self): + self.flakes(''' + def foo(x): + fns = [lambda x: x + 1, lambda x: x + 2, lambda x: x + 3] + return [(x := fn(x)) for fn in fns] + + ''') + + def test_assign_expr_nested(self): + """Test assignment expressions in nested expressions.""" + self.flakes(''' + if ([(y:=x) for x in range(4) if [(z:=q) for q in range(4)]]): + print(y) + print(z) + + ''') + + +class TestStringFormatting(TestCase): + + @others + + ''', m.FStringMissingPlaceholders) + self.flakes(''' + print( + f'foo' + f'bar' + ) + ''', m.FStringMissingPlaceholders) + # this is an "escaped placeholder" but not a placeholder + self.flakes("f'{{}}'", m.FStringMissingPlaceholders) + # ok: f-string with placeholders + self.flakes(''' + x = 5 + print(f'{x}') + ''') + # ok: f-string with format specifiers + self.flakes(''' + x = 'a' * 90 + print(f'{x:.8}') + ''') + # ok: f-string with multiple format specifiers + self.flakes(''' + x = y = 5 + print(f'{x:>2} {y:>2}') + ''') + + def test_invalid_dot_format_calls(self): + self.flakes(''' + '{'.format(1) + + ''', m.StringDotFormatInvalidFormat) + self.flakes(''' + '{} {1}'.format(1, 2) + ''', m.StringDotFormatMixingAutomatic) + self.flakes(''' + '{0} {}'.format(1, 2) + ''', m.StringDotFormatMixingAutomatic) + self.flakes(''' + '{}'.format(1, 2) + ''', m.StringDotFormatExtraPositionalArguments) + self.flakes(''' + '{}'.format(1, bar=2) + ''', m.StringDotFormatExtraNamedArguments) + self.flakes(''' + '{} {}'.format(1) + ''', m.StringDotFormatMissingArgument) + self.flakes(''' + '{2}'.format() + ''', m.StringDotFormatMissingArgument) + self.flakes(''' + '{bar}'.format() + ''', m.StringDotFormatMissingArgument) + # too much string recursion (placeholder-in-placeholder) + self.flakes(''' + '{:{:{}}}'.format(1, 2, 3) + ''', m.StringDotFormatInvalidFormat) + # ok: dotted / bracketed names need to handle the param differently + self.flakes("'{.__class__}'.format('')") + self.flakes("'{foo[bar]}'.format(foo={'bar': 'barv'})") + # ok: placeholder-placeholders + self.flakes(''' + print('{:{}} {}'.format(1, 15, 2)) + ''') + # ok: not a placeholder-placeholder + self.flakes(''' + print('{:2}'.format(1)) + ''') + # ok: not mixed automatic + self.flakes(''' + '{foo}-{}'.format(1, foo=2) + ''') + # ok: we can't determine statically the format args + self.flakes(''' + a = () + "{}".format(*a) + ''') + self.flakes(''' + k = {} + "{foo}".format(**k) + ''') + + def test_invalid_percent_format_calls(self): + self.flakes(''' + '%(foo)' % {'foo': 'bar'} + + ''', m.PercentFormatInvalidFormat) + self.flakes(''' + '%s %(foo)s' % {'foo': 'bar'} + ''', m.PercentFormatMixedPositionalAndNamed) + self.flakes(''' + '%(foo)s %s' % {'foo': 'bar'} + ''', m.PercentFormatMixedPositionalAndNamed) + self.flakes(''' + '%j' % (1,) + ''', m.PercentFormatUnsupportedFormatCharacter) + self.flakes(''' + '%s %s' % (1,) + ''', m.PercentFormatPositionalCountMismatch) + self.flakes(''' + '%s %s' % (1, 2, 3) + ''', m.PercentFormatPositionalCountMismatch) + self.flakes(''' + '%(bar)s' % {} + ''', m.PercentFormatMissingArgument,) + self.flakes(''' + '%(bar)s' % {'bar': 1, 'baz': 2} + ''', m.PercentFormatExtraNamedArguments) + self.flakes(''' + '%(bar)s' % (1, 2, 3) + ''', m.PercentFormatExpectedMapping) + self.flakes(''' + '%s %s' % {'k': 'v'} + ''', m.PercentFormatExpectedSequence) + self.flakes(''' + '%(bar)*s' % {'bar': 'baz'} + ''', m.PercentFormatStarRequiresSequence) + # ok: single %s with mapping + self.flakes(''' + '%s' % {'foo': 'bar', 'baz': 'womp'} + ''') + # ok: does not cause a MemoryError (the strings aren't evaluated) + self.flakes(''' + "%1000000000000f" % 1 + ''') + # ok: %% should not count towards placeholder count + self.flakes(''' + '%% %s %% %s' % (1, 2) + ''') + # ok: * consumes one positional argument + self.flakes(''' + '%.*f' % (2, 1.1234) + '%*.*f' % (5, 2, 3.1234) + ''') + + def test_ok_percent_format_cannot_determine_element_count(self): + self.flakes(''' + a = [] + '%s %s' % [*a] + '%s %s' % (*a,) + + ''') + self.flakes(''' + k = {} + '%(k)s' % {**k} + ''') + + +class TestAsyncStatements(TestCase): + + @others + + ''') + + def test_asyncDefAwait(self): + self.flakes(''' + async def read_data(db): + await db.fetch('SELECT ...') + + ''') + + def test_asyncDefUndefined(self): + self.flakes(''' + async def bar(): + return foo() + + ''', m.UndefinedName) + + def test_asyncFor(self): + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + output.append(row) + return output + + ''') + + def test_asyncForUnderscoreLoopVar(self): + self.flakes(''' + async def coro(it): + async for _ in it: + pass + + ''') + + def test_loopControlInAsyncFor(self): + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + if row[0] == 'skip': + continue + output.append(row) + return output + + ''') + + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + if row[0] == 'stop': + break + output.append(row) + return output + ''') + + def test_loopControlInAsyncForElse(self): + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + output.append(row) + else: + continue + return output + + ''', m.ContinueOutsideLoop) + + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + output.append(row) + else: + break + return output + ''', m.BreakOutsideLoop) + + def test_asyncWith(self): + self.flakes(''' + async def commit(session, data): + async with session.transaction(): + await session.update(data) + + ''') + + def test_asyncWithItem(self): + self.flakes(''' + async def commit(session, data): + async with session.transaction() as trans: + await trans.begin() + ... + await trans.end() + + ''') + + def test_matmul(self): + self.flakes(''' + def foo(a, b): + return a @ b + + ''') + + def test_formatstring(self): + self.flakes(''' + hi = 'hi' + mom = 'mom' + f'{hi} {mom}' + + ''') + + def test_raise_notimplemented(self): + self.flakes(''' + raise NotImplementedError("This is fine") + + ''') + + self.flakes(''' + raise NotImplementedError + ''') + + self.flakes(''' + raise NotImplemented("This isn't gonna work") + ''', m.RaiseNotImplemented) + + self.flakes(''' + raise NotImplemented + ''', m.RaiseNotImplemented) + + +class TestIncompatiblePrintOperator(TestCase): + """ + Tests for warning about invalid use of print function. + """ + + @others + + ''') + + def test_invalid_print_when_imported_from_future(self): + exc = self.flakes(''' + from __future__ import print_function + import sys + print >>sys.stderr, "Hello" + + ''', m.InvalidPrintSyntax).messages[0] + + self.assertEqual(exc.lineno, 4) + self.assertEqual(exc.col, 0) + + def test_print_augmented_assign(self): + # nonsense, but shouldn't crash pyflakes + self.flakes('print += 1') + + + def test_print_function_assignment(self): + """ + A valid assignment, tested for catching false positives. + """ + self.flakes(''' + from __future__ import print_function + log = print + log("Hello") + +@path pyflakes +__version__ = '3.2.0' +@language python +@tabwidth -4 + + ''') + + def test_print_in_lambda(self): + self.flakes(''' + from __future__ import print_function + a = lambda: print + + ''') + + def test_print_returned_in_function(self): + self.flakes(''' + from __future__ import print_function + def a(): + return print + + ''') + + def test_print_as_condition_test(self): + self.flakes(''' + from __future__ import print_function + if print: pass + +def test_duplicateArgs(self): + self.flakes('def fu(bar, bar): pass', m.DuplicateArgument) + + +def test_localReferencedBeforeAssignment(self): + self.flakes(''' + a = 1 + def f(): + a; a=1 + f() + +def test_unusedVariable(self): + """ + Warn when a variable in a function is assigned a value that's never + used. + """ + self.flakes(''' + def a(): + b = 1 + +def test_f_string_without_placeholders(self): + self.flakes("f'foo'", m.FStringMissingPlaceholders) + self.flakes(''' + f"""foo + bar + """ + +def test_asyncDef(self): + self.flakes(''' + async def bar(): + return 42 + +def test_valid_print(self): + self.flakes(''' + print("Hello") + +@path pyflakes/test +""" +Tests for behaviour related to type annotations. +""" + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skipIf + + +@others + """) +@language python +@tabwidth -4 + +class TestTypeAnnotations(TestCase): + + @others + + """) + + def test_typingExtensionsOverload(self): + """Allow intentional redefinitions via @typing_extensions.overload""" + self.flakes(""" + import typing_extensions + from typing_extensions import overload + + @overload + def f(s: None) -> None: + pass + + @overload + def f(s: int) -> int: + pass + + def f(s): + return s + + @typing_extensions.overload + def g(s: None) -> None: + pass + + @typing_extensions.overload + def g(s: int) -> int: + pass + + def g(s): + return s + + """) + + def test_typingOverloadAsync(self): + """Allow intentional redefinitions via @typing.overload (async)""" + self.flakes(""" + from typing import overload + + @overload + async def f(s: None) -> None: + pass + + @overload + async def f(s: int) -> int: + pass + + async def f(s): + return s + + """) + + def test_overload_with_multiple_decorators(self): + self.flakes(""" + from typing import overload + dec = lambda f: f + + @dec + @overload + def f(x: int) -> int: + pass + + @dec + @overload + def f(x: str) -> str: + pass + + @dec + def f(x): return x + + """) + + def test_overload_in_class(self): + self.flakes(""" + from typing import overload + + class C: + @overload + def f(self, x: int) -> int: + pass + + @overload + def f(self, x: str) -> str: + pass + + def f(self, x): return x + + """) + + def test_aliased_import(self): + """Detect when typing is imported as another name""" + self.flakes(""" + import typing as t + + @t.overload + def f(s: None) -> None: + pass + + @t.overload + def f(s: int) -> int: + pass + + def f(s): + return s + + """) + + def test_not_a_typing_overload(self): + """regression test for @typing.overload detection bug in 2.1.0""" + self.flakes(""" + def foo(x): + return x + + @foo + def bar(): + pass + + def bar(): + pass + + """, m.RedefinedWhileUnused) + + def test_variable_annotations(self): + self.flakes(''' + name: str + age: int + + ''') + self.flakes(''' + name: str = 'Bob' + age: int = 18 + ''') + self.flakes(''' + class C: + name: str + age: int + ''') + self.flakes(''' + class C: + name: str = 'Bob' + age: int = 18 + ''') + self.flakes(''' + def f(): + name: str + age: int + ''', m.UnusedAnnotation, m.UnusedAnnotation) + self.flakes(''' + def f(): + name: str = 'Bob' + age: int = 18 + foo: not_a_real_type = None + ''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable, m.UndefinedName) + self.flakes(''' + def f(): + name: str + print(name) + ''', m.UndefinedName) + self.flakes(''' + from typing import Any + def f(): + a: Any + ''', m.UnusedAnnotation) + self.flakes(''' + foo: not_a_real_type + ''', m.UndefinedName) + self.flakes(''' + foo: not_a_real_type = None + ''', m.UndefinedName) + self.flakes(''' + class C: + foo: not_a_real_type + ''', m.UndefinedName) + self.flakes(''' + class C: + foo: not_a_real_type = None + ''', m.UndefinedName) + self.flakes(''' + def f(): + class C: + foo: not_a_real_type + ''', m.UndefinedName) + self.flakes(''' + def f(): + class C: + foo: not_a_real_type = None + ''', m.UndefinedName) + self.flakes(''' + from foo import Bar + bar: Bar + ''') + self.flakes(''' + from foo import Bar + bar: 'Bar' + ''') + self.flakes(''' + import foo + bar: foo.Bar + ''') + self.flakes(''' + import foo + bar: 'foo.Bar' + ''') + self.flakes(''' + from foo import Bar + def f(bar: Bar): pass + ''') + self.flakes(''' + from foo import Bar + def f(bar: 'Bar'): pass + ''') + self.flakes(''' + from foo import Bar + def f(bar) -> Bar: return bar + ''') + self.flakes(''' + from foo import Bar + def f(bar) -> 'Bar': return bar + ''') + self.flakes(''' + bar: 'Bar' + ''', m.UndefinedName) + self.flakes(''' + bar: 'foo.Bar' + ''', m.UndefinedName) + self.flakes(''' + from foo import Bar + bar: str + ''', m.UnusedImport) + self.flakes(''' + from foo import Bar + def f(bar: str): pass + ''', m.UnusedImport) + self.flakes(''' + def f(a: A) -> A: pass + class A: pass + ''', m.UndefinedName, m.UndefinedName) + self.flakes(''' + def f(a: 'A') -> 'A': return a + class A: pass + ''') + self.flakes(''' + a: A + class A: pass + ''', m.UndefinedName) + self.flakes(''' + a: 'A' + class A: pass + ''') + self.flakes(''' + T: object + def f(t: T): pass + ''', m.UndefinedName) + self.flakes(''' + T: object + def g(t: 'T'): pass + ''') + self.flakes(''' + a: 'A B' + ''', m.ForwardAnnotationSyntaxError) + self.flakes(''' + a: 'A; B' + ''', m.ForwardAnnotationSyntaxError) + self.flakes(''' + a: '1 + 2' + ''') + self.flakes(''' + a: 'a: "A"' + ''', m.ForwardAnnotationSyntaxError) + + def test_variable_annotation_references_self_name_undefined(self): + self.flakes(""" + x: int = x + + """, m.UndefinedName) + + def test_TypeAlias_annotations(self): + self.flakes(""" + from typing_extensions import TypeAlias + from foo import Bar + + bar: TypeAlias = Bar + +def redefines(self, other): + return ( + super().redefines(other) or + (isinstance(other, Assignment) and self.name == other.name) + ) + + + + """) + self.flakes(""" + from typing_extensions import TypeAlias + from foo import Bar + + bar: TypeAlias = 'Bar' + """) + self.flakes(""" + from typing_extensions import TypeAlias + from foo import Bar + + class A: + bar: TypeAlias = Bar + """) + self.flakes(""" + from typing_extensions import TypeAlias + from foo import Bar + + class A: + bar: TypeAlias = 'Bar' + """) + self.flakes(""" + from typing_extensions import TypeAlias + + bar: TypeAlias + """) + self.flakes(""" + from typing_extensions import TypeAlias + from foo import Bar + + bar: TypeAlias + """, m.UnusedImport) + + def test_annotating_an_import(self): + self.flakes(''' + from a import b, c + b: c + print(b) + + ''') + + def test_unused_annotation(self): + # Unused annotations are fine in module and class scope + self.flakes(''' + x: int + class Cls: + y: int + + ''') + self.flakes(''' + def f(): + x: int + ''', m.UnusedAnnotation) + # This should only print one UnusedVariable message + self.flakes(''' + def f(): + x: int + x = 3 + ''', m.UnusedVariable) + + def test_unused_annotation_in_outer_scope_reassigned_in_local_scope(self): + self.flakes(''' + x: int + x.__dict__ + def f(): x = 1 + + ''', m.UndefinedName, m.UnusedVariable) + + def test_unassigned_annotation_is_undefined(self): + self.flakes(''' + name: str + print(name) + + ''', m.UndefinedName) + + def test_annotated_async_def(self): + self.flakes(''' + class c: pass + async def func(c: c) -> None: pass + + ''') + + def test_postponed_annotations(self): + self.flakes(''' + from __future__ import annotations + def f(a: A) -> A: pass + class A: + b: B + class B: pass + + ''') + + self.flakes(''' + from __future__ import annotations + def f(a: A) -> A: pass + class A: + b: Undefined + class B: pass + ''', m.UndefinedName) + + self.flakes(''' + from __future__ import annotations + T: object + def f(t: T): pass + def g(t: 'T'): pass + ''') + + def test_type_annotation_clobbers_all(self): + self.flakes('''\ + from typing import TYPE_CHECKING, List + + from y import z + + if not TYPE_CHECKING: + __all__ = ("z",) + else: + __all__: List[str] + + ''') + + def test_return_annotation_is_class_scope_variable(self): + self.flakes(""" + from typing import TypeVar + class Test: + Y = TypeVar('Y') + + def t(self, x: Y) -> Y: + return x + + """) + + def test_return_annotation_is_function_body_variable(self): + self.flakes(""" + class Test: + def t(self) -> Y: + Y = 2 + return Y + + """, m.UndefinedName) + + def test_positional_only_argument_annotations(self): + self.flakes(""" + from x import C + + def f(c: C, /): ... + +def __init__(self, name): + super().__init__(name, None) + + + """) + + def test_partially_quoted_type_annotation(self): + self.flakes(""" + from queue import Queue + from typing import Optional + + def f() -> Optional['Queue[str]']: + return None + + """) + + def test_partially_quoted_type_assignment(self): + self.flakes(""" + from queue import Queue + from typing import Optional + + MaybeQueue = Optional['Queue[str]'] + + """) + + def test_nested_partially_quoted_type_assignment(self): + self.flakes(""" + from queue import Queue + from typing import Callable + + Func = Callable[['Queue[str]'], None] + + """) + + def test_quoted_type_cast(self): + self.flakes(""" + from typing import cast, Optional + + maybe_int = cast('Optional[int]', 42) + + """) + + def test_type_cast_literal_str_to_str(self): + # Checks that our handling of quoted type annotations in the first + # argument to `cast` doesn't cause issues when (only) the _second_ + # argument is a literal str which looks a bit like a type annotation. + self.flakes(""" + from typing import cast + + a_string = cast(str, 'Optional[int]') + + """) + + def test_quoted_type_cast_renamed_import(self): + self.flakes(""" + from typing import cast as tsac, Optional as Maybe + + maybe_int = tsac('Maybe[int]', 42) + + """) + + def test_quoted_TypeVar_constraints(self): + self.flakes(""" + from typing import TypeVar, Optional + + T = TypeVar('T', 'str', 'Optional[int]', bytes) + + """) + + def test_quoted_TypeVar_bound(self): + self.flakes(""" + from typing import TypeVar, Optional, List + + T = TypeVar('T', bound='Optional[int]') + S = TypeVar('S', int, bound='List[int]') + + """) + + def test_literal_type_typing(self): + self.flakes(""" + from typing import Literal + + def f(x: Literal['some string']) -> None: + return None + + """) + + def test_literal_type_typing_extensions(self): + self.flakes(""" + from typing_extensions import Literal + + def f(x: Literal['some string']) -> None: + return None + +def __repr__(self): + return '<{} object {!r} at 0x{:x}>'.format( + self.__class__.__name__, + self.name, + id(self) + ) + + + + """) + + def test_annotated_type_typing_missing_forward_type(self): + self.flakes(""" + from typing import Annotated + + def f(x: Annotated['integer']) -> None: + return None + + """, m.UndefinedName) + + def test_annotated_type_typing_missing_forward_type_multiple_args(self): + self.flakes(""" + from typing import Annotated + + def f(x: Annotated['integer', 1]) -> None: + return None + + """, m.UndefinedName) + + def test_annotated_type_typing_with_string_args(self): + self.flakes(""" + from typing import Annotated + + def f(x: Annotated[int, '> 0']) -> None: + return None + + """) + + def test_annotated_type_typing_with_string_args_in_union(self): + self.flakes(""" + from typing import Annotated, Union + + def f(x: Union[Annotated['int', '>0'], 'integer']) -> None: + return None + + """, m.UndefinedName) + + def test_literal_type_some_other_module(self): + """err on the side of false-negatives for types named Literal""" + self.flakes(""" + from my_module import compat + from my_module.compat import Literal + + def f(x: compat.Literal['some string']) -> None: + return None + def g(x: Literal['some string']) -> None: + return None + + """) + + def test_literal_union_type_typing(self): + self.flakes(""" + from typing import Literal + + def f(x: Literal['some string', 'foo bar']) -> None: + return None + + """) + + def test_deferred_twice_annotation(self): + self.flakes(""" + from queue import Queue + from typing import Optional + + + def f() -> "Optional['Queue[str]']": + return None + + """) + + def test_partial_string_annotations_with_future_annotations(self): + self.flakes(""" + from __future__ import annotations + + from queue import Queue + from typing import Optional + + + def f() -> Optional['Queue[str]']: + return None + + """) + + def test_forward_annotations_for_classes_in_scope(self): + # see #749 + self.flakes(""" + from typing import Optional + + def f(): + class C: + a: "D" + b: Optional["D"] + c: "Optional[D]" + + class D: pass + + """) + + def test_idomiatic_typing_guards(self): + # typing.TYPE_CHECKING: python3.5.3+ + self.flakes(""" + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from t import T + + def f() -> T: + pass + +def __init__(self, item): + self.name = item.id + + + """) + # False: the old, more-compatible approach + self.flakes(""" + if False: + from t import T + + def f() -> T: + pass + """) + # some choose to assign a constant and do it that way + self.flakes(""" + MYPY = False + + if MYPY: + from t import T + + def f() -> T: + pass + """) + + def test_typing_guard_for_protocol(self): + self.flakes(""" + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from typing import Protocol + else: + Protocol = object + + class C(Protocol): + def f() -> int: + pass + + """) + + def test_typednames_correct_forward_ref(self): + self.flakes(""" + from typing import TypedDict, List, NamedTuple + + List[TypedDict("x", {})] + List[TypedDict("x", x=int)] + List[NamedTuple("a", a=int)] + List[NamedTuple("a", [("a", int)])] + + """) + self.flakes(""" + from typing import TypedDict, List, NamedTuple, TypeVar + + List[TypedDict("x", {"x": "Y"})] + List[TypedDict("x", x="Y")] + List[NamedTuple("a", [("a", "Y")])] + List[NamedTuple("a", a="Y")] + List[TypedDict("x", {"x": List["a"]})] + List[TypeVar("A", bound="C")] + List[TypeVar("A", List["C"])] + """, *[m.UndefinedName]*7) + self.flakes(""" + from typing import NamedTuple, TypeVar, cast + from t import A, B, C, D, E + + NamedTuple("A", [("a", A["C"])]) + TypeVar("A", bound=A["B"]) + TypeVar("A", A["D"]) + cast(A["E"], []) + """) + + def test_namedtypes_classes(self): + self.flakes(""" + from typing import TypedDict, NamedTuple + class X(TypedDict): + y: TypedDict("z", {"zz":int}) + + class Y(NamedTuple): + y: NamedTuple("v", [("vv", int)]) + + """) + + @skipIf(version_info < (3, 11), 'new in Python 3.11') + def test_variadic_generics(self): + self.flakes(""" + from typing import Generic + from typing import TypeVarTuple + + Ts = TypeVarTuple('Ts') + + class Shape(Generic[*Ts]): pass + + def f(*args: *Ts) -> None: ... + + def g(x: Shape[*Ts]) -> Shape[*Ts]: ... + + """) + + @skipIf(version_info < (3, 12), 'new in Python 3.12') + def test_type_statements(self): + self.flakes(""" + type ListOrSet[T] = list[T] | set[T] + + def f(x: ListOrSet[str]) -> None: ... + + type RecursiveType = int | list[RecursiveType] + + type ForwardRef = int | C + + type ForwardRefInBounds[T: C] = T + + class C: pass + + """) + + @skipIf(version_info < (3, 12), 'new in Python 3.12') + def test_type_parameters_functions(self): + self.flakes(""" + def f[T](t: T) -> T: return t + + async def g[T](t: T) -> T: return t + + def with_forward_ref[T: C](t: T) -> T: return t + + def can_access_inside[T](t: T) -> T: + print(T) + return t + + class C: pass + + """) + + @skipIf(version_info < (3, 12), 'new in Python 3.12') + def test_type_parameters_do_not_escape_function_scopes(self): + self.flakes(""" + from x import g + + @g(T) # not accessible in decorators + def f[T](t: T) -> T: return t + + T # not accessible afterwards + + """, m.UndefinedName, m.UndefinedName) + + @skipIf(version_info < (3, 12), 'new in Python 3.12') + def test_type_parameters_classes(self): + self.flakes(""" + class C[T](list[T]): pass + + class UsesForward[T: Forward](list[T]): pass + + class Forward: pass + + class WithinBody[T](list[T]): + t = T + + """) + + @skipIf(version_info < (3, 12), 'new in Python 3.12') + def test_type_parameters_do_not_escape_class_scopes(self): + self.flakes(""" + from x import g + + @g(T) # not accessible in decorators + class C[T](list[T]): pass + + T # not accessible afterwards + + """, m.UndefinedName, m.UndefinedName) + + @skipIf(version_info < (3, 12), 'new in Python 3.12') + def test_type_parameters_TypeVarTuple(self): + self.flakes(""" + def f[*T](*args: *T) -> None: ... + +def __eq__(self, compare): + return ( + compare.__class__ == self.__class__ and + compare.name == self.name + ) + + + """) + + @skipIf(version_info < (3, 12), 'new in Python 3.12') + def test_type_parameters_ParamSpec(self): + self.flakes(""" + from typing import Callable + + def f[R, **P](f: Callable[P, R]) -> Callable[P, R]: + def g(*args: P.args, **kwargs: P.kwargs) -> R: + return f(*args, **kwargs) + return g + +def test_typingOverload(self): + """Allow intentional redefinitions via @typing.overload""" + self.flakes(""" + import typing + from typing import overload + + @overload + def f(s: None) -> None: + pass + + @overload + def f(s: int) -> int: + pass + + def f(s): + return s + + @typing.overload + def g(s: None) -> None: + pass + + @typing.overload + def g(s: int) -> int: + pass + + def g(s): + return s + +@path pyflakes/test +import ast + +from pyflakes import messages as m, checker +from pyflakes.test.harness import TestCase, skip + + +@others +@language python +@tabwidth -4 + +class Test(TestCase): + @others + + ''', + m.UndefinedName) + + def test_undefinedExceptionName(self): + """Exception names can't be used after the except: block. + + The exc variable is unused inside the exception handler.""" + self.flakes(''' + try: + raise ValueError('ve') + except ValueError as exc: + pass + exc + + ''', m.UndefinedName, m.UnusedVariable) + + def test_namesDeclaredInExceptBlocks(self): + """Locals declared in except: blocks can be used after the block. + + This shows the example in test_undefinedExceptionName is + different.""" + self.flakes(''' + try: + raise ValueError('ve') + except ValueError as exc: + e = exc + e + + ''') + + @skip('error reporting disabled due to false positives below') + def test_undefinedExceptionNameObscuringLocalVariable(self): + """Exception names obscure locals, can't be used after. + + Last line will raise UnboundLocalError on Python 3 after exiting + the except: block. Note next two examples for false positives to + watch out for.""" + self.flakes(''' + exc = 'Original value' + try: + raise ValueError('ve') + except ValueError as exc: + pass + exc + + ''', + m.UndefinedName) + + def test_undefinedExceptionNameObscuringLocalVariable2(self): + """Exception names are unbound after the `except:` block. + + Last line will raise UnboundLocalError. + The exc variable is unused inside the exception handler. + """ + self.flakes(''' + try: + raise ValueError('ve') + except ValueError as exc: + pass + print(exc) + exc = 'Original value' + + ''', m.UndefinedName, m.UnusedVariable) + + def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self): + """Exception names obscure locals, can't be used after. Unless. + + Last line will never raise UnboundLocalError because it's only + entered if no exception was raised.""" + self.flakes(''' + exc = 'Original value' + try: + raise ValueError('ve') + except ValueError as exc: + print('exception logged') + raise + exc + + ''', m.UnusedVariable) + + def test_delExceptionInExcept(self): + """The exception name can be deleted in the except: block.""" + self.flakes(''' + try: + pass + except Exception as exc: + del exc + +def __hash__(self): + return hash(self.name) + + + + ''') + + def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self): + """Exception names obscure locals, can't be used after. Unless. + + Last line will never raise UnboundLocalError because `error` is + only falsy if the `except:` block has not been entered.""" + self.flakes(''' + exc = 'Original value' + error = None + try: + raise ValueError('ve') + except ValueError as exc: + error = 'exception logged' + if error: + print(error) + else: + exc + + ''', m.UnusedVariable) + + @skip('error reporting disabled due to false positives below') + def test_undefinedExceptionNameObscuringGlobalVariable(self): + """Exception names obscure globals, can't be used after. + + Last line will raise UnboundLocalError because the existence of that + exception name creates a local scope placeholder for it, obscuring any + globals, etc.""" + self.flakes(''' + exc = 'Original value' + def func(): + try: + pass # nothing is raised + except ValueError as exc: + pass # block never entered, exc stays unbound + exc + + ''', + m.UndefinedLocal) + + @skip('error reporting disabled due to false positives below') + def test_undefinedExceptionNameObscuringGlobalVariable2(self): + """Exception names obscure globals, can't be used after. + + Last line will raise NameError on Python 3 because the name is + locally unbound after the `except:` block, even if it's + nonlocal. We should issue an error in this case because code + only working correctly if an exception isn't raised, is invalid. + Unless it's explicitly silenced, see false positives below.""" + self.flakes(''' + exc = 'Original value' + def func(): + global exc + try: + raise ValueError('ve') + except ValueError as exc: + pass # block never entered, exc stays unbound + exc + + ''', + m.UndefinedLocal) + + def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1(self): + """Exception names obscure globals, can't be used after. Unless. + + Last line will never raise NameError because it's only entered + if no exception was raised.""" + self.flakes(''' + exc = 'Original value' + def func(): + global exc + try: + raise ValueError('ve') + except ValueError as exc: + print('exception logged') + raise + exc + + ''', m.UnusedVariable) + + def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2(self): + """Exception names obscure globals, can't be used after. Unless. + + Last line will never raise NameError because `error` is only + falsy if the `except:` block has not been entered.""" + self.flakes(''' + exc = 'Original value' + def func(): + global exc + error = None + try: + raise ValueError('ve') + except ValueError as exc: + error = 'exception logged' + if error: + print(error) + else: + exc + + ''', m.UnusedVariable) + + def test_functionsNeedGlobalScope(self): + self.flakes(''' + class a: + def b(): + fu + fu = 1 + + ''') + + def test_builtins(self): + self.flakes('range(10)') + + + def test_builtinWindowsError(self): + """ + C{WindowsError} is sometimes a builtin name, so no warning is emitted + for using it. + """ + self.flakes('WindowsError') + + + def test_moduleAnnotations(self): + """ + Use of the C{__annotations__} in module scope should not emit + an undefined name warning when version is greater than or equal to 3.6. + """ + self.flakes('__annotations__') + + + def test_magicGlobalsFile(self): + """ + Use of the C{__file__} magic global should not emit an undefined name + warning. + """ + self.flakes('__file__') + + +def __init__(self, name, source, full_name=None): + self.fullName = full_name or name + self.redefined = [] + super().__init__(name, source) + + + def test_magicGlobalsBuiltins(self): + """ + Use of the C{__builtins__} magic global should not emit an undefined + name warning. + """ + self.flakes('__builtins__') + + + def test_magicGlobalsName(self): + """ + Use of the C{__name__} magic global should not emit an undefined name + warning. + """ + self.flakes('__name__') + + + def test_magicGlobalsPath(self): + """ + Use of the C{__path__} magic global should not emit an undefined name + warning, if you refer to it from a file called __init__.py. + """ + self.flakes('__path__', m.UndefinedName) + self.flakes('__path__', filename='package/__init__.py') + + + def test_magicModuleInClassScope(self): + """ + Use of the C{__module__} magic builtin should not emit an undefined + name warning if used in class scope. + """ + self.flakes('__module__', m.UndefinedName) + self.flakes(''' + class Foo: + __module__ + + ''') + self.flakes(''' + class Foo: + def bar(self): + __module__ + ''', m.UndefinedName) + + def test_magicQualnameInClassScope(self): + """ + Use of the C{__qualname__} magic builtin should not emit an undefined + name warning if used in class scope. + """ + self.flakes('__qualname__', m.UndefinedName) + self.flakes(''' + class Foo: + __qualname__ + + ''') + self.flakes(''' + class Foo: + def bar(self): + __qualname__ + ''', m.UndefinedName) + + def test_globalImportStar(self): + """Can't find undefined names with import *.""" + self.flakes('from fu import *; bar', + m.ImportStarUsed, m.ImportStarUsage) + + + def test_definedByGlobal(self): + """ + "global" can make an otherwise undefined name in another function + defined. + """ + self.flakes(''' + def a(): global fu; fu = 1 + def b(): fu + + ''') + self.flakes(''' + def c(): bar + def b(): global bar; bar = 1 + ''') + + def test_definedByGlobalMultipleNames(self): + """ + "global" can accept multiple names. + """ + self.flakes(''' + def a(): global fu, bar; fu = 1; bar = 2 + def b(): fu; bar + + ''') + + def test_globalInGlobalScope(self): + """ + A global statement in the global scope is ignored. + """ + self.flakes(''' + global x + def foo(): + print(x) + + ''', m.UndefinedName) + + def test_global_reset_name_only(self): + """A global statement does not prevent other names being undefined.""" + # Only different undefined names are reported. + # See following test that fails where the same name is used. + self.flakes(''' + def f1(): + s + + def f2(): + global m + +def redefines(self, other): + if isinstance(other, SubmoduleImportation): + # See note in SubmoduleImportation about RedefinedWhileUnused + return self.fullName == other.fullName + return isinstance(other, Definition) and self.name == other.name + + + ''', m.UndefinedName) + + @skip("todo") + def test_unused_global(self): + """An unused global statement does not define the name.""" + self.flakes(''' + def f1(): + m + + def f2(): + global m + + ''', m.UndefinedName) + + def test_del(self): + """Del deletes bindings.""" + self.flakes('a = 1; del a; a', m.UndefinedName) + + + def test_delGlobal(self): + """Del a global binding from a function.""" + self.flakes(''' + a = 1 + def f(): + global a + del a + a + + ''') + + def test_delUndefined(self): + """Del an undefined name.""" + self.flakes('del a', m.UndefinedName) + + + def test_delConditional(self): + """ + Ignores conditional bindings deletion. + """ + self.flakes(''' + context = None + test = True + if False: + del(test) + assert(test) + + ''') + + def test_delConditionalNested(self): + """ + Ignored conditional bindings deletion even if they are nested in other + blocks. + """ + self.flakes(''' + context = None + test = True + if False: + with context(): + del(test) + assert(test) + + ''') + + def test_delWhile(self): + """ + Ignore bindings deletion if called inside the body of a while + statement. + """ + self.flakes(''' + def test(): + foo = 'bar' + while False: + del foo + assert(foo) + + ''') + + def test_delWhileTestUsage(self): + """ + Ignore bindings deletion if called inside the body of a while + statement and name is used inside while's test part. + """ + self.flakes(''' + def _worker(): + o = True + while o is not True: + del o + o = False + + ''') + + def test_delWhileNested(self): + """ + Ignore bindings deletions if node is part of while's test, even when + del is in a nested block. + """ + self.flakes(''' + context = None + def _worker(): + o = True + while o is not True: + while True: + with context(): + del o + o = False + + ''') + + def test_globalFromNestedScope(self): + """Global names are available from nested scopes.""" + self.flakes(''' + a = 1 + def b(): + def c(): + a + +@path pyflakes +from pyflakes.api import main + +# python -m pyflakes +if __name__ == '__main__': + main(prog='pyflakes') +@language python +@tabwidth -4 + +def _has_alias(self): + """Return whether importation needs an as clause.""" + return not self.fullName.split('.')[-1] == self.name + + + ''') + + def test_laterRedefinedGlobalFromNestedScope(self): + """ + Test that referencing a local name that shadows a global, before it is + defined, generates a warning. + """ + self.flakes(''' + a = 1 + def fun(): + a + a = 2 + return a + + ''', m.UndefinedLocal) + + def test_laterRedefinedGlobalFromNestedScope2(self): + """ + Test that referencing a local name in a nested scope that shadows a + global declared in an enclosing scope, before it is defined, generates + a warning. + """ + self.flakes(''' + a = 1 + def fun(): + global a + def fun2(): + a + a = 2 + return a + + ''', m.UndefinedLocal) + + def test_intermediateClassScopeIgnored(self): + """ + If a name defined in an enclosing scope is shadowed by a local variable + and the name is used locally before it is bound, an unbound local + warning is emitted, even if there is a class scope between the enclosing + scope and the local scope. + """ + self.flakes(''' + def f(): + x = 1 + class g: + def h(self): + a = x + x = None + print(x, a) + print(x) + + ''', m.UndefinedLocal) + + def test_doubleNestingReportsClosestName(self): + """ + Test that referencing a local name in a nested scope that shadows a + variable declared in two different outer scopes before it is defined + in the innermost scope generates an UnboundLocal warning which + refers to the nearest shadowed name. + """ + exc = self.flakes(''' + def a(): + x = 1 + def b(): + x = 2 # line 5 + def c(): + x + x = 3 + return x + return x + return x + + ''', m.UndefinedLocal).messages[0] + + # _DoctestMixin.flakes adds two lines preceding the code above. + expected_line_num = 7 if self.withDoctest else 5 + + self.assertEqual(exc.message_args, ('x', expected_line_num)) + + def test_laterRedefinedGlobalFromNestedScope3(self): + """ + Test that referencing a local name in a nested scope that shadows a + global, before it is defined, generates a warning. + """ + self.flakes(''' + def fun(): + a = 1 + def fun2(): + a + a = 1 + return a + return a + + ''', m.UndefinedLocal) + + def test_undefinedAugmentedAssignment(self): + self.flakes( + ''' + def f(seq): + a = 0 + seq[a] += 1 + seq[b] /= 2 + c[0] *= 2 + a -= 3 + d += 4 + e[any] = 5 + + ''', + m.UndefinedName, # b + m.UndefinedName, # c + m.UndefinedName, m.UnusedVariable, # d + m.UndefinedName, # e + ) + + def test_nestedClass(self): + """Nested classes can access enclosing scope.""" + self.flakes(''' + def f(foo): + class C: + bar = foo + def f(self): + return foo + return C() + + f(123).f() + + ''') + + def test_badNestedClass(self): + """Free variables in nested classes must bind at class creation.""" + self.flakes(''' + def f(): + class C: + bar = foo + foo = 456 + return foo + f() + + ''', m.UndefinedName) + + def test_definedAsStarArgs(self): + """Star and double-star arg names are defined.""" + self.flakes(''' + def f(a, *b, **c): + print(a, b, c) + + ''') + + def test_definedAsStarUnpack(self): + """Star names in unpack are defined.""" + self.flakes(''' + a, *b = range(10) + print(a, b) + +@property +def source_statement(self): + """Generate a source statement equivalent to the import.""" + if self._has_alias(): + return f'import {self.fullName} as {self.name}' + else: + return 'import %s' % self.fullName + + + ''') + self.flakes(''' + *a, b = range(10) + print(a, b) + ''') + self.flakes(''' + a, *b, c = range(10) + print(a, b, c) + ''') + + def test_usedAsStarUnpack(self): + """ + Star names in unpack are used if RHS is not a tuple/list literal. + """ + self.flakes(''' + def f(): + a, *b = range(10) + + ''') + self.flakes(''' + def f(): + (*a, b) = range(10) + ''') + self.flakes(''' + def f(): + [a, *b, c] = range(10) + ''') + + def test_unusedAsStarUnpack(self): + """ + Star names in unpack are unused if RHS is a tuple/list literal. + """ + self.flakes(''' + def f(): + a, *b = any, all, 4, 2, 'un' + + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + (*a, b) = [bool, int, float, complex] + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + [a, *b, c] = 9, 8, 7, 6, 5, 4 + ''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable) + + def test_keywordOnlyArgs(self): + """Keyword-only arg names are defined.""" + self.flakes(''' + def f(*, a, b=None): + print(a, b) + + ''') + + self.flakes(''' + import default_b + def f(*, a, b=default_b): + print(a, b) + ''') + + def test_keywordOnlyArgsUndefined(self): + """Typo in kwonly name.""" + self.flakes(''' + def f(*, a, b=default_c): + print(a, b) + + ''', m.UndefinedName) + + def test_annotationUndefined(self): + """Undefined annotations.""" + self.flakes(''' + from abc import note1, note2, note3, note4, note5 + def func(a: note1, *args: note2, + b: note3=12, **kw: note4) -> note5: pass + + ''') + + self.flakes(''' + def func(): + d = e = 42 + def func(a: {1, d}) -> (lambda c: e): pass + ''') + + def test_metaClassUndefined(self): + self.flakes(''' + from abc import ABCMeta + class A(metaclass=ABCMeta): pass + + ''') + + def test_definedInGenExp(self): + """ + Using the loop variable of a generator expression results in no + warnings. + """ + self.flakes('(a for a in [1, 2, 3] if a)') + + self.flakes('(b for b in (a for a in [1, 2, 3] if a) if b)') + + + def test_undefinedInGenExpNested(self): + """ + The loop variables of generator expressions nested together are + not defined in the other generator. + """ + self.flakes('(b for b in (a for a in [1, 2, 3] if b) if b)', + m.UndefinedName) + + self.flakes('(b for b in (a for a in [1, 2, 3] if a) if a)', + m.UndefinedName) + + + def test_undefinedWithErrorHandler(self): + """ + Some compatibility code checks explicitly for NameError. + It should not trigger warnings. + """ + self.flakes(''' + try: + socket_map + except NameError: + socket_map = {} + + ''') + self.flakes(''' + try: + _memoryview.contiguous + except (NameError, AttributeError): + raise RuntimeError("Python >= 3.3 is required") + ''') + # If NameError is not explicitly handled, generate a warning + self.flakes(''' + try: + socket_map + except: + socket_map = {} + ''', m.UndefinedName) + self.flakes(''' + try: + socket_map + except Exception: + socket_map = {} + ''', m.UndefinedName) + + def test_definedInClass(self): + """ + Defined name for generator expressions and dict/set comprehension. + """ + self.flakes(''' + class A: + T = range(10) + + Z = (x for x in T) + L = [x for x in T] + B = dict((i, str(i)) for i in T) + +def __str__(self): + """Return import full name with alias.""" + if self._has_alias(): + return self.fullName + ' as ' + self.name + else: + return self.fullName + + + + ''') + + self.flakes(''' + class A: + T = range(10) + + X = {x for x in T} + Y = {x:x for x in T} + ''') + + def test_definedInClassNested(self): + """Defined name for nested generator expressions in a class.""" + self.flakes(''' + class A: + T = range(10) + + Z = (x for x in (a for a in T)) + + ''') + + def test_undefinedInLoop(self): + """ + The loop variable is defined after the expression is computed. + """ + self.flakes(''' + for i in range(i): + print(i) + + ''', m.UndefinedName) + self.flakes(''' + [42 for i in range(i)] + ''', m.UndefinedName) + self.flakes(''' + (42 for i in range(i)) + ''', m.UndefinedName) + + def test_definedFromLambdaInDictionaryComprehension(self): + """ + Defined name referenced from a lambda function within a dict/set + comprehension. + """ + self.flakes(''' + {lambda: id(x) for x in range(10)} + + ''') + + def test_definedFromLambdaInGenerator(self): + """ + Defined name referenced from a lambda function within a generator + expression. + """ + self.flakes(''' + any(lambda: id(x) for x in range(10)) + + ''') + + def test_undefinedFromLambdaInDictionaryComprehension(self): + """ + Undefined name referenced from a lambda function within a dict/set + comprehension. + """ + self.flakes(''' + {lambda: id(y) for x in range(10)} + + ''', m.UndefinedName) + + def test_undefinedFromLambdaInComprehension(self): + """ + Undefined name referenced from a lambda function within a generator + expression. + """ + self.flakes(''' + any(lambda: id(y) for x in range(10)) + + ''', m.UndefinedName) + + def test_dunderClass(self): + code = ''' + class Test(object): + def __init__(self): + print(__class__.__name__) + self.x = 1 + + t = Test() + ''' + self.flakes(code) + + + +class NameTests(TestCase): + """ + Tests for some extra cases of name handling. + """ + @others + +def test_undefined(self): + self.flakes('bar', m.UndefinedName) + + +def test_definedInListComp(self): + self.flakes('[a for a in range(10) if a]') + + +def __init__(self, name, source): + # A dot should only appear in the name when it is a submodule import + assert '.' in name and (not source or isinstance(source, ast.Import)) + package_name = name.split('.')[0] + super().__init__(package_name, source) + self.fullName = name + + +def test_undefinedInListComp(self): + self.flakes(''' + [a for a in range(10)] + a + +def test_impossibleContext(self): + """ + A Name node with an unrecognized context results in a RuntimeError being + raised. + """ + tree = ast.parse("x = 10") + # Make it into something unrecognizable. + tree.body[0].targets[0].ctx = object() + self.assertRaises(RuntimeError, checker.Checker, tree) + +def redefines(self, other): + if isinstance(other, Importation): + return self.fullName == other.fullName + return super().redefines(other) + + +def __str__(self): + return self.fullName + + +@property +def source_statement(self): + return 'import ' + self.fullName + + + +def __init__(self, name, source, module, real_name=None): + self.module = module + self.real_name = real_name or name + + if module.endswith('.'): + full_name = module + self.real_name + else: + full_name = module + '.' + self.real_name + + super().__init__(name, source, full_name) + + +def __str__(self): + """Return import full name with alias.""" + if self.real_name != self.name: + return self.fullName + ' as ' + self.name + else: + return self.fullName + + +@property +def source_statement(self): + if self.real_name != self.name: + return f'from {self.module} import {self.real_name} as {self.name}' + else: + return f'from {self.module} import {self.name}' + + + +@path pyflakes +""" +API for the command-line I{pyflakes} tool. +""" +import ast +import os +import platform +import re +import sys + +from pyflakes import checker, __version__ +from pyflakes import reporter as modReporter + +__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main'] + +PYTHON_SHEBANG_REGEX = re.compile(br'^#!.*\bpython(3(\.\d+)?|w)?[dmu]?\s') + + +@others +@language python +@tabwidth -4 + +def __init__(self, name, source): + super().__init__('*', source) + # Each star importation needs a unique name, and + # may not be the module name otherwise it will be deemed imported + self.name = name + '.*' + self.fullName = name + + +@property +def source_statement(self): + return 'from ' + self.fullName + ' import *' + + +def __str__(self): + # When the module ends with a ., avoid the ambiguous '..*' + if self.fullName.endswith('.'): + return self.source_statement + else: + return self.name + + + +def __init__(self, name, source, scope): + super().__init__(name, source, '__future__') + self.used = (scope, source) + + + +def redefines(self, other): + """An Annotation doesn't define any name, so it cannot redefine one.""" + return False + + + +def __init__(self, name, source, scope): + if '__all__' in scope and isinstance(source, ast.AugAssign): + self.names = list(scope['__all__'].names) + else: + self.names = [] + + def _add_to_names(container): + for node in container.elts: + if isinstance(node, ast.Constant) and isinstance(node.value, str): + self.names.append(node.value) + + if isinstance(source.value, (ast.List, ast.Tuple)): + _add_to_names(source.value) + # If concatenating lists or tuples + elif isinstance(source.value, ast.BinOp): + currentValue = source.value + while isinstance(currentValue.right, (ast.List, ast.Tuple)): + left = currentValue.left + right = currentValue.right + _add_to_names(right) + # If more lists are being added + if isinstance(left, ast.BinOp): + currentValue = left + # If just two lists are being added + elif isinstance(left, (ast.List, ast.Tuple)): + _add_to_names(left) + # All lists accounted for - done + break + # If not list concatenation + else: + break + super().__init__(name, source) + + + +usesLocals = False +alwaysUsed = {'__tracebackhide__', '__traceback_info__', + '__traceback_supplement__', '__debuggerskip__'} + +def __init__(self): + super().__init__() + # Simplify: manage the special locals as globals + self.globals = self.alwaysUsed.copy() + self.returnValue = None # First non-empty return + + +def unused_assignments(self): + """ + Return a generator for the assignments which have not been used. + """ + for name, binding in self.items(): + if (not binding.used and + name != '_' and # see issue #202 + name not in self.globals and + not self.usesLocals and + isinstance(binding, Assignment)): + yield name, binding + + +def unused_annotations(self): + """ + Return a generator for the annotations which have not been used. + """ + for name, binding in self.items(): + if not binding.used and isinstance(binding, Annotation): + yield name, binding + + + + + +@language rest +@wrap + +Goals: +- Understand pyflakes. +- Add global code that trusts annotations. + +@language python + + + +FOR_TYPES = (ast.For, ast.AsyncFor) + + + +MAPPING_KEY_RE = re.compile(r'\(([^()]*)\)') +CONVERSION_FLAG_RE = re.compile('[#0+ -]*') +WIDTH_RE = re.compile(r'(?:\*|\d*)') +PRECISION_RE = re.compile(r'(?:\.(?:\*|\d*))?') +LENGTH_RE = re.compile('[hlL]?') +# https://docs.python.org/3/library/stdtypes.html#old-string-formatting +VALID_CONVERSIONS = frozenset('diouxXeEfFgGcrsa%') + + + +# Globally defined names which are not attributes of the builtins module, or +# are only present on some platforms. +_MAGIC_GLOBALS = ['__file__', '__builtins__', '__annotations__', 'WindowsError'] + + + +TYPING_MODULES = frozenset(('typing', 'typing_extensions')) + + + + + + + +_ast_node_scope = { + ast.Module: ModuleScope, + ast.ClassDef: ClassScope, + ast.FunctionDef: FunctionScope, + ast.AsyncFunctionDef: FunctionScope, + ast.Lambda: FunctionScope, + ast.ListComp: GeneratorScope, + ast.SetComp: GeneratorScope, + ast.GeneratorExp: GeneratorScope, + ast.DictComp: GeneratorScope, +} + +nodeDepth = 0 +offset = None +_in_annotation = AnnotationState.NONE + +builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) +_customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') +if _customBuiltIns: + builtIns.update(_customBuiltIns.split(',')) +del _customBuiltIns + + + + + +PYPY = hasattr(sys, 'pypy_version_info') + +builtin_vars = dir(builtins) + +parse_format_string = string.Formatter().parse + + +@nosearch + +# Word, Head, Body + +# found 7 nodes +# additional node types +COMPREHENSION = KEYWORD = FORMATTEDVALUE = handleChildren + + + + + + +@nosearch + +# Word, Head, Body + +# found 22 nodes + + + + + diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 1036434e..2fe7f37a 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -164,6 +164,7 @@ def __missing__(self, node_class): self[node_class] = fields = self._get_fields(node_class) return fields + def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): """ Yield all direct child nodes of *node*, that is, all fields that From fcd508ba05aca082b924821242dff1d915598860 Mon Sep 17 00:00:00 2001 From: "Edward K. Ream" Date: Wed, 26 Jun 2024 04:55:33 -0500 Subject: [PATCH 4/4] Oops: delete the .leo file --- ekr_pyflakes.leo | 13701 --------------------------------------------- 1 file changed, 13701 deletions(-) delete mode 100644 ekr_pyflakes.leo diff --git a/ekr_pyflakes.leo b/ekr_pyflakes.leo deleted file mode 100644 index 825da851..00000000 --- a/ekr_pyflakes.leo +++ /dev/null @@ -1,13701 +0,0 @@ - - - - - - - - -Startup -@persistence -@settings - Buttons -@ignore Disabled buttons -@button clean-text -@button create decorators -create_d -create_decorator -create_decorators -create_fixups -find_class -find_next_clone -munge_lines -run - -@button introspect -@button join-path -@button make-decorators2 -create_d -create_decorator -create_decorators V2 -define_s -munge_lines -run V2 - -@button print-gnx -@button print-ss -@button print-ua -@button rclick-test -@rclick hi - -@button set-ua -@button split-path -@button timeit -@button toggle-debug-app -@button unit-tests -@command test @key=Ctrl-F7 - -@@button show-uas -@@button count-children - -@bool allow-text-zoom = True -@bool check-python-code-on-write = True -@bool use-german-keyboard = False -@bool use-mouse-expand-gestures = False -@data exec-script-commands -@data exec-script-patterns -@data history-list -@ignore Chapters -@chapter 1 -abc node 1 -child - -cloned node in chapter - -@chapter 2 -Chapter two -Second node - - - -@chapter 3 - -@rclick say-hi @args=say hi -Abbreviation settings -@bool enable-abbreviations = True -@outline-data tree-abbreviations -@organizer 1 -@organizer 2 -demo;; -@@button MyDemo @key=Ctrl-9 -<< imports >> -script_string -class myDemo -wrappers - - - -per-commander-plugin;; -@@file pluginname.py -<< docstring >> -<< version history >> -<< imports >> -init -onCreate -class pluginController -__init__ - - - - -importer;; -@@file importers/{|{x=name}|}.py -class {|{x=cap_name}|}_Importer -{|{x=name}|}.Overrides -{|{x=name}|}.clean_headline -{|{x=name}|}.clean_nodes - - -class class {|{x=cap_name}|}_ScanState -{|{x=name}|}_state.level -{|{x=name}|}_state.update - - - - - -Appearance settings -@bool log-pane-wraps = False -@bool recent-files-group-always = True -@bool show-iconbar = True -@bool show-tips = False -@bool stayInTreeAfterSelect = True -@bool use-chapter-tabs = True -@bool use-chapters = True -@bool use-gutter = False -@int qweb-view-font-size = 30 -@string initial-split-orientation = vertical - -Coloring settings -@bool color-doc-parts-as-rest = True -@bool use-pygments = False -@bool use-pygments-styles = False -@color head-bg = @mistyrose2 -@string pygments-style-name = leonine -@string target-language = python - -Command settings -@bool create-at-persistence-nodes-automatically = True -@bool enable-persistence = True -@bool make-node-conflicts-node = True -@bool run-pyflakes-on-write = True -@bool use-jedi = True -@bool use-qcompleter = False -@bool warn-about-redefined-shortcuts = True -@int auto-justify = 80 -rst3 path options -@string rst3-write-intermediate-extension = .txt -@string rst3-default-path = None -@string rst3-stylesheet-name = default.css -@string rst3-stylesheet-path = None -@string rst3-publish-argv-for-missing-stylesheets = None - - -Declutter -@bool tree-declutter = False -@data tree-declutter-patterns ---- unused patterns - About Decluttering -Rule & replacement lines -Style lines - -declutter: add icon to folders and remove... -declutter: demo pattern -declutter: hide org-mode tags -declutter: replace @<file> with an icon -declutter: show last part of long filenames - - -Environment settings -@ifenv COMPUTERNAME, edreamleo-pc, other-pc -@bool computername = True - -@ifenv xyzzy, abc -@bool xyzzy = True - -@ifplatform win32,linux2 -@string platform = not-mac - - -File settings -@bool open-with-clean-filenames = True -@bool check-for-changed-external-files = True -@bool open-with-save-on-update = False -@bool open-with-uses-derived-file-extensions = True - -Find settings -@bool auto-scroll-find-tab = False -@bool close-find-dialog-after-search = False -@bool find-ignore-duplicates = False -@bool minibuffer-find-mode = True -@bool use-find-dialog = False - -Importer settings -@data import-html-tags -@data import-xml-tags - -make-stub-files settings -@bool stub-overwrite = True -@bool stub-trace-matches = False -@bool stub-trace-patterns = False -@bool stub-trace-reduce = False -@bool stub-trace-visitors = False -@bool stub-update = False -@data stub-def-name-patterns -@data stub-general-patterns -@data stub-prefix-lines -@string stub-output-directory = ~/stubs - -Plugins -@@@enabled-plugins -mod_http settings -@bool http-active = True -@int port = 8080 -@string rst-http-attributename = 'rst_http_attribute' - -viewrendered settings -@bool view-rendered-auto-create = False -@bool view-rendered-auto-hide = False -@string view-rendered-default-kind = rst - -wikiview plugin -@data wikiview-link-patterns -@bool wikiview-active = True - - -Scintilla settings -@bool qt-use-scintilla = False - -Sphinx settings -@string sphinx-command-directory = -@string sphinx-default-command = make html -@string sphinx-input-directory = None -@string sphinx-output-directory = None - -Syntax coloring settings -@@color rest.keyword2 = red -@@color rest.keyword4 = blue -@@color rest.leokeyword = green -@color forth.keyword3 = black -@color python.name = @solarized-yellow -@font rest.comment1 - -Vim mode -@string vim-mode-normal-border = border: 3px solid #268bd2 -@string vim-mode-insert-border = border: 3px solid #dc322f -@string vim-mode-visual-border = border: 3px solid gray -@string vim-mode-unfocused-border = border: 3px dashed #268bd2 -@bool vim-mode = False - - - -Buttons -@button backup -@ignore Disabled buttons -@button print-gnx - - -script: remove copyright -script: Recursive import - -*** To do -Files -@clean __init__.py -@clean __main__.py -@clean api.py -function: check -function: checkPath -function: isPythonFile -function: iterSourceCode -function: checkRecursive -function: _exitOnSignal -function: _get_version -function: main - -@clean checker.py -<< checker.py: globals >> -checker.py: utils -function: getAlternatives -const: FOR_TYPES -function: _is_singleton -function: _is_tuple_constant -function: _is_constant -function: _is_const_non_singleton -function: _is_name_or_attr -const: *_RE & VALID_CONVERSIONS -function: _must_match -function: parse_percent_format -class _FieldsOrder(dict) -function: iter_child_nodes (Uses _FieldsOrder) -function: convert_to_value -function: is_notimplemented_name_node - -class Binding -class Definition(Binding) -Definition.redefines - -class Builtin(Definition) -Builtin.__init__ -Builtin.__repr__ - -class UnhandledKeyType -class VariableKey -VariableKey.__init__ -VariableKey.__eq__ -VariableKey.__hash__ - -checker.py: Importation class & subclasses -class Importation(Definition) -Importation.__init__ -Importation.redefines -Importation._has_alias -Importation.source_statement -Importation.__str__ - -class SubmoduleImportation(Importation) -SubmoduleImportation.__init__ -SubmoduleImportation.redefines -SubmoduleImportation.__str__ -SubmoduleImportation.source_statement - -class ImportationFrom(Importation) -ImportationFrom.__init__ -ImportationFrom.__str__ -ImportationFrom.source_statement - -class StarImportation(Importation) -StarImportation.__init__ -StarImportation.source_statement -StarImportation.__str__ - -class FutureImportation(ImportationFrom) -FutureImportation.__init__ - - -class Argument(Binding) -class Assignment(Binding) & NamedExprAssignment(Assignment) -class Annotation(Binding) -Annotation.redefines - -class FunctionDefinition(Definition) -class ClassDefinition(Definition) -class ExportBinding(Binding) -ExportBinding.__init__ - -checker.py: Scopes -class Scope(dict) -class ClassScope(Scope) -class FunctionScope(Scope) -FunctionScope.__init__ -FunctionScope.unused_assignments -FunctionScope.unused_annotations - -class TypeScope(Scope) -class GeneratorScope(Scope) -class ModuleScope(Scope) -class DoctestScope(ModuleScope) -class DetectClassScopedMagic -const: _MAGIC_GLOBALS - -function: getNodeName -checker.py: Typing & Annotations -const: TYPING_MODULES -function: _is_typing_helper -function: _is_typing -function: _is_any_typing_member -function: is_typing_overload -class AnnotationState -function: in_annotation -function: in_string_annotation - -class Checker -<< Checker: class data >> -Checker.__init__ -Checker: Deferred functions -Checker.deferFunction -Checker._run_deferred - -Checker._in_doctest -Checker: Properties -Checker.futuresAllowed -Checker.annotationsFutureEnabled -Checker.scope & in_scope - -Checker: Utils -Checker.checkDeadScopes -Checker.report -Checker: Tree utils -Checker.getParent -Checker.getCommonAncestor -Checker.descendantOf -Checker._getAncestor -Checker.getScopeNode -Checker.differentForks - -Checker.addBinding -Checker._unknown_handler -Checker.getNodeHandler -Checker: handleNodeLoad/Store/Delete -Checker.handleNodeLoad -Checker.handleNodeStore -Checker.handleNodeDelete - -Checker._enter_annotation -Checker._in_postponed_annotation -Checker.handleChildren -Checker: is* -Checker.isLiteralTupleUnpacking -Checker.isDocstring - -Checker.getDocstring -Checker: handle* -Checker.handleNode -Checker.handleDoctests -Checker.handleStringAnnotation -Checker.handle_annotation_always_deferred -Checker.handleAnnotation - -Checker.ignore - -Checker: visitors & helpers -Checker.SUBSCRIPT -Checker._handle_string_dot_format -Checker.CALL -Checker._handle_percent_format -Checker.BINOP -Checker.CONSTANT & related operators -Checker.RAISE -Checker: additional node types -Checker.JOINEDSTR -Checker.DICT -Checker.IF & IFEXPR -Checker.ASSERT -Checker.GLOBAL & NONLOCAL -Checker.GENERATOREXP & *COMP -Checker.NAME -Checker.CONTINUE & BREAK -Checker.RETURN -Checker.YIELD -Checker.FUNCTIONDEF -Checker.LAMBDA -Checker.ARGUMENTS -Checker.ARG -Checker.CLASSDEF -Checker.AUGASSIGN -Checker.TUPLE & LIST -Checker.IMPORT -Checker.IMPORTFROM -Checker.TRY & TRYSTAR -Checker.EXCEPTHANDLER -Checker.ANNASSIGN -Checker.COMPARE -Checker.MATCH* & _match_target -Checker._type_param_scope (@contextlib.contextmanager) -Checker.TYPEVAR, PARAMSPEC & TYPEVARTUPLE -Checker.TYPEALIAS - - - -@clean messages.py -class Message -class UnusedImport -class RedefinedWhileUnused -class ImportShadowedByLoopVar -ImportShadowedByLoopVar.__init__ - -class ImportStarNotPermitted -ImportStarNotPermitted.__init__ - -class ImportStarUsed -ImportStarUsed.__init__ - -class ImportStarUsage -ImportStarUsage.__init__ - -class UndefinedName -UndefinedName.__init__ - -class DoctestSyntaxError -DoctestSyntaxError.__init__ - -class UndefinedExport -UndefinedExport.__init__ - -class UndefinedLocal -UndefinedLocal.__init__ - -class DuplicateArgument -DuplicateArgument.__init__ - -class MultiValueRepeatedKeyLiteral -MultiValueRepeatedKeyLiteral.__init__ - -class MultiValueRepeatedKeyVariable -MultiValueRepeatedKeyVariable.__init__ - -class LateFutureImport -class FutureFeatureNotDefined -FutureFeatureNotDefined.__init__ - -class UnusedVariable -UnusedVariable.__init__ - -class UnusedAnnotation -UnusedAnnotation.__init__ - -class ReturnOutsideFunction -class YieldOutsideFunction -class ContinueOutsideLoop -class BreakOutsideLoop -class DefaultExceptNotLast -class TwoStarredExpressions -class TooManyExpressionsInStarredAssignment -class IfTuple -class AssertTuple -class ForwardAnnotationSyntaxError -ForwardAnnotationSyntaxError.__init__ - -class RaiseNotImplemented -class InvalidPrintSyntax -class IsLiteral -class FStringMissingPlaceholders -class StringDotFormatExtraPositionalArguments -StringDotFormatExtraPositionalArguments.__init__ - -class StringDotFormatExtraNamedArguments -StringDotFormatExtraNamedArguments.__init__ - -class StringDotFormatMissingArgument -StringDotFormatMissingArgument.__init__ - -class StringDotFormatMixingAutomatic -class StringDotFormatInvalidFormat -StringDotFormatInvalidFormat.__init__ - -class PercentFormatInvalidFormat -PercentFormatInvalidFormat.__init__ - -class PercentFormatMixedPositionalAndNamed -class PercentFormatUnsupportedFormatCharacter -PercentFormatUnsupportedFormatCharacter.__init__ - -class PercentFormatPositionalCountMismatch -PercentFormatPositionalCountMismatch.__init__ - -class PercentFormatExtraNamedArguments -PercentFormatExtraNamedArguments.__init__ - -class PercentFormatMissingArgument -PercentFormatMissingArgument.__init__ - -class PercentFormatExpectedMapping -class PercentFormatExpectedSequence -class PercentFormatStarRequiresSequence - -@clean reporter.py -class Reporter -Reporter.__init__ -Reporter.unexpectedError -Reporter.syntaxError -Reporter.flake - -function: _makeDefaultReporter - -@clean setup.py -function: get_version -function: get_long_description - -@edit .gitignore -pyflakes/test -@@@clean __init__.py -@clean harness.py -class TestCase -TestCase.flakes - - -@clean test_api.py -function: withStderrTo -class Node -Node.__init__ - -class SysStreamCapturing -SysStreamCapturing.__init__ -SysStreamCapturing.__enter__ -SysStreamCapturing.__exit__ - -class LoggingReporter -LoggingReporter.__init__ -LoggingReporter.flake -LoggingReporter.unexpectedError -LoggingReporter.syntaxError - -class TestIterSourceCode -TestIterSourceCode.setUp -TestIterSourceCode.tearDown -TestIterSourceCode.makeEmptyFile -TestIterSourceCode.test_emptyDirectory -TestIterSourceCode.test_singleFile -TestIterSourceCode.test_onlyPythonSource -TestIterSourceCode.test_recurses -TestIterSourceCode.test_shebang -TestIterSourceCode.test_multipleDirectories -TestIterSourceCode.test_explicitFiles - -class TestReporter -TestReporter.test_syntaxError -TestReporter.test_syntaxErrorNoOffset -TestReporter.test_syntaxErrorNoText -TestReporter.test_multiLineSyntaxError -TestReporter.test_unexpectedError -TestReporter.test_flake - -class CheckTests -CheckTests.makeTempFile -CheckTests.assertHasErrors -CheckTests.getErrors -CheckTests.test_legacyScript -CheckTests.test_missingTrailingNewline -CheckTests.test_checkPathNonExisting -CheckTests.test_multilineSyntaxError -CheckTests.test_eofSyntaxError -CheckTests.test_eofSyntaxErrorWithTab ---- -CheckTests.test_nonDefaultFollowsDefaultSyntaxError -CheckTests.test_nonKeywordAfterKeywordSyntaxError -CheckTests.test_invalidEscape -CheckTests.test_permissionDenied -CheckTests.test_pyflakesWarning -CheckTests.test_encodedFileUTF8 -CheckTests.test_CRLFLineEndings -CheckTests.test_misencodedFileUTF8 -CheckTests.test_misencodedFileUTF16 -CheckTests.test_checkRecursive -CheckTests.test_stdinReportsErrors - -class IntegrationTests -IntegrationTests.setUp -IntegrationTests.tearDown -IntegrationTests.getPyflakesBinary -IntegrationTests.runPyflakes -IntegrationTests.test_goodFile -IntegrationTests.test_fileWithFlakes -IntegrationTests.test_errors_io -IntegrationTests.test_errors_syntax -IntegrationTests.xxx_test_readFromStdin - -class TestMain -TestMain.runPyflakes - - -@clean test_builtin.py -class TestBuiltins -TestBuiltins.test_builtin_unbound_local - -function: test_global_shadowing_builtin - -@clean test_code_segment.py -class TestCodeSegments -TestCodeSegments.test_function_segment - -function: test_class_segment -function: test_scope_class -function: test_scope_function -function: test_scope_async_function - -@clean test_dict.py -class Test -Test.test_duplicate_keys -Test.test_duplicate_keys_bytes_vs_unicode_py3 -Test.test_duplicate_values_bytes_vs_unicode_py3 -Test.test_multiple_duplicate_keys -Test.test_duplicate_keys_in_function - -function: test_duplicate_keys_in_lambda -function: test_duplicate_keys_tuples -function: test_duplicate_keys_tuples_int_and_float -function: test_duplicate_keys_ints -function: test_duplicate_keys_bools -function: test_duplicate_keys_bools_false -function: test_duplicate_keys_none -function: test_duplicate_variable_keys -function: test_duplicate_variable_values -function: test_duplicate_variable_values_same_value -function: test_duplicate_key_float_and_int -function: test_no_duplicate_key_error_same_value -function: test_no_duplicate_key_errors -function: test_no_duplicate_keys_tuples_same_first_element -function: test_no_duplicate_key_errors_func_call -function: test_no_duplicate_key_errors_bool_or_none -function: test_no_duplicate_key_errors_ints -function: test_no_duplicate_key_errors_vars -function: test_no_duplicate_key_errors_tuples -function: test_no_duplicate_key_errors_instance_attributes - -@clean test_doctests.py -class _DoctestMixin -_DoctestMixin.doctestify - -function: flakes -class Test -Test.test_scope_class - -function: test_nested_doctest_ignored -function: test_global_module_scope_pollution -function: test_global_undefined -function: test_nested_class -function: test_ignore_nested_function -function: test_inaccessible_scope_class -function: test_importBeforeDoctest -function: test_importBeforeAndInDoctest -function: test_importInDoctestAndAfter -function: test_offsetInDoctests -function: test_offsetInLambdasInDoctests -function: test_offsetAfterDoctests -function: test_syntaxErrorInDoctest -function: test_indentationErrorInDoctest -function: test_offsetWithMultiLineArgs -function: test_doctestCanReferToFunction -function: test_doctestCanReferToClass -function: test_noOffsetSyntaxErrorInDoctest -function: test_singleUnderscoreInDoctest -function: test_globalUnderscoreInDoctest -class TestOther -class TestImports -class TestUndefinedNames - -@clean test_imports.py -class TestImportationObject -TestImportationObject.test_import_basic -TestImportationObject.test_import_as -TestImportationObject.test_import_submodule -TestImportationObject.test_import_submodule_as -TestImportationObject.test_import_submodule_as_source_name -TestImportationObject.test_importfrom_relative -TestImportationObject.test_importfrom_relative_parent -TestImportationObject.test_importfrom_relative_with_module -TestImportationObject.test_importfrom_relative_with_module_as -TestImportationObject.test_importfrom_member -TestImportationObject.test_importfrom_submodule_member -TestImportationObject.test_importfrom_member_as -TestImportationObject.test_importfrom_submodule_member_as -TestImportationObject.test_importfrom_star -TestImportationObject.test_importfrom_star_relative -TestImportationObject.test_importfrom_future -TestImportationObject.test_unusedImport_underscore - -class Test -Test.test_unusedImport -Test.test_unusedImport_relative -Test.test_aliasedImport -Test.test_aliasedImportShadowModule -Test.test_usedImport -Test.test_usedImport_relative -Test.test_redefinedWhileUnused -Test.test_redefinedIf - -function: test_redefinedIfElse -function: test_redefinedTry -function: test_redefinedTryExcept -function: test_redefinedTryNested -function: test_redefinedTryExceptMulti -function: test_redefinedTryElse -function: test_redefinedTryExceptElse -function: test_redefinedTryExceptFinally -function: test_redefinedTryExceptElseFinally -function: test_redefinedByFunction -function: test_redefinedInNestedFunction -function: test_redefinedInNestedFunctionTwice -function: test_redefinedButUsedLater -function: test_redefinedByClass -function: test_redefinedBySubclass -function: test_redefinedInClass -function: test_importInClass -function: test_usedInFunction -function: test_shadowedByParameter -function: test_newAssignment -function: test_usedInGetattr -function: test_usedInSlice -function: test_usedInIfBody -function: test_usedInIfConditional -function: test_usedInElifConditional -function: test_usedInElse -function: test_usedInCall -function: test_usedInClass -function: test_usedInClassBase -function: test_notUsedInNestedScope -function: test_usedInFor -function: test_usedInForElse -function: test_redefinedByFor -function: test_shadowedByFor -function: test_shadowedByForDeep -function: test_usedInReturn -function: test_usedInOperators -function: test_usedInAssert -function: test_usedInSubscript -function: test_usedInLogic -function: test_usedInList -function: test_usedInTuple -function: test_usedInTry -function: test_usedInExcept -function: test_redefinedByExcept -function: test_usedInRaise -function: test_usedInYield -function: test_usedInDict -function: test_usedInParameterDefault -function: test_usedInAttributeAssign -function: test_usedInKeywordArg -function: test_usedInAssignment -function: test_usedInListComp -function: test_usedInTryFinally -function: test_usedInWhile -function: test_usedInGlobal -function: test_usedAndGlobal -function: test_assignedToGlobal -function: test_usedInExec -function: test_usedInLambda -function: test_shadowedByLambda -function: test_usedInSliceObj -function: test_unusedInNestedScope -function: test_methodsDontUseClassScope -function: test_nestedFunctionsNestScope -function: test_nestedClassAndFunctionScope -function: test_importStar -function: test_importStar_relative -function: test_localImportStar -function: test_packageImport -function: test_unusedPackageImport -function: test_duplicateSubmoduleImport -function: test_differentSubmoduleImport -function: test_used_package_with_submodule_import -function: test_used_package_with_submodule_import_of_alias -function: test_unused_package_with_submodule_import -function: test_assignRHSFirst -function: test_tryingMultipleImports -function: test_nonGlobalDoesNotRedefine -function: test_functionsRunLater -function: test_functionNamesAreBoundNow -function: test_ignoreNonImportRedefinitions -function: test_importingForImportError -function: test_importedInClass -function: test_importUsedInMethodDefinition -function: test_futureImport -function: test_futureImportFirst -function: test_futureImportUsed -function: test_futureImportUndefined -function: test_futureImportStar -class TestSpecialAll -TestSpecialAll.test_ignoredInFunction - -function: test_ignoredInClass -function: test_ignored_when_not_directly_assigned -function: test_warningSuppressed -function: test_augmentedAssignment -function: test_list_concatenation_assignment -function: test_tuple_concatenation_assignment -function: test_all_with_attributes -function: test_all_with_names -function: test_all_with_attributes_added -function: test_all_mixed_attributes_and_strings -function: test_unboundExported -function: test_importStarExported -function: test_importStarNotExported -function: test_usedInGenExp -function: test_redefinedByGenExp -function: test_usedAsDecorator -function: test_usedAsClassDecorator - -@clean test_is_literal.py -class Test -Test.test_is_str - -function: test_is_bytes -function: test_is_unicode -function: test_is_int -function: test_is_true -function: test_is_false -function: test_is_not_str -function: test_is_not_bytes -function: test_is_not_unicode -function: test_is_not_int -function: test_is_not_true -function: test_is_not_false -function: test_left_is_str -function: test_left_is_bytes -function: test_left_is_unicode -function: test_left_is_int -function: test_left_is_true -function: test_left_is_false -function: test_left_is_not_str -function: test_left_is_not_bytes -function: test_left_is_not_unicode -function: test_left_is_not_int -function: test_left_is_not_true -function: test_left_is_not_false -function: test_chained_operators_is_true -function: test_chained_operators_is_str -function: test_chained_operators_is_true_end -function: test_chained_operators_is_str_end -function: test_is_tuple_constant -function: test_is_tuple_constant_containing_constants -function: test_is_tuple_containing_variables_ok - -@clean test_match.py -class TestMatch -TestMatch.test_match_bindings - -function: test_match_pattern_matched_class -function: test_match_placeholder -function: test_match_singleton -function: test_match_or_pattern -function: test_match_star -function: test_match_double_star -function: test_defined_in_different_branches - -@clean test_other.py -class Test -Test.test_duplicateArgs -Test.test_localReferencedBeforeAssignment - -function: test_redefinedInGenerator -function: test_redefinedInSetComprehension -function: test_redefinedInDictComprehension -function: test_redefinedFunction -function: test_redefined_function_shadows_variable -function: test_redefinedUnderscoreFunction -function: test_redefinedUnderscoreImportation -function: test_redefinedClassFunction -function: test_redefinedIfElseFunction -function: test_redefinedIfFunction -function: test_redefinedTryExceptFunction -function: test_redefinedTryFunction -function: test_redefinedIfElseInListComp -function: test_functionDecorator -function: test_classFunctionDecorator -function: test_modernProperty -function: test_unaryPlus -function: test_undefinedBaseClass -function: test_classNameUndefinedInClassBody -function: test_classNameDefinedPreviously -function: test_classRedefinition -function: test_functionRedefinedAsClass -function: test_classRedefinedAsFunction -function: test_classWithReturn -function: test_moduleWithReturn -function: test_classWithYield -function: test_moduleWithYield -function: test_classWithYieldFrom -function: test_moduleWithYieldFrom -function: test_continueOutsideLoop -function: test_continueInsideLoop -function: test_breakOutsideLoop -function: test_breakInsideLoop -function: test_defaultExceptLast -function: test_defaultExceptNotLast -function: test_starredAssignmentNoError -function: test_starredAssignmentErrors -function: test_doubleAssignment -function: test_doubleAssignmentConditionally -function: test_doubleAssignmentWithUse -function: test_comparison -function: test_identity -function: test_containment -function: test_loopControl -function: test_ellipsis -function: test_extendedSlice -function: test_varAugmentedAssignment -function: test_attrAugmentedAssignment -function: test_globalDeclaredInDifferentScope -function: test_function_arguments -function: test_function_arguments_python3 -class TestUnusedAssignment -TestUnusedAssignment.test_unusedVariable - -function: test_unusedUnderscoreVariable -function: test_unusedVariableAsLocals -function: test_unusedVariableNoLocals -function: test_unusedReassignedVariable -function: test_variableUsedInLoop -function: test_assignToGlobal -function: test_assignToNonlocal -function: test_assignToMember -function: test_assignInForLoop -function: test_assignInListComprehension -function: test_generatorExpression -function: test_assignmentInsideLoop -function: test_tupleUnpacking -function: test_listUnpacking -function: test_closedOver -function: test_doubleClosedOver -function: test_tracebackhideSpecialVariable -function: test_debuggerskipSpecialVariable -function: test_ifexp -function: test_if_tuple -function: test_withStatementNoNames -function: test_withStatementSingleName -function: test_withStatementAttributeName -function: test_withStatementSubscript -function: test_withStatementSubscriptUndefined -function: test_withStatementTupleNames -function: test_withStatementListNames -function: test_withStatementComplicatedTarget -function: test_withStatementSingleNameUndefined -function: test_withStatementTupleNamesUndefined -function: test_withStatementSingleNameRedefined -function: test_withStatementTupleNamesRedefined -function: test_withStatementUndefinedInside -function: test_withStatementNameDefinedInBody -function: test_withStatementUndefinedInExpression -function: test_dictComprehension -function: test_setComprehensionAndLiteral -function: test_exceptionUsedInExcept -function: test_exceptionUnusedInExcept -function: test_exception_unused_in_except_star -function: test_exceptionUnusedInExceptInFunction -function: test_exceptWithoutNameInFunction -function: test_exceptWithoutNameInFunctionTuple -function: test_augmentedAssignmentImportedFunctionCall -function: test_assert_without_message -function: test_assert_with_message -function: test_assert_tuple -function: test_assert_tuple_empty -function: test_assert_static -function: test_yieldFromUndefined -function: test_f_string -function: test_assign_expr -function: test_assign_expr_generator_scope -function: test_assign_expr_generator_scope_reassigns_parameter -function: test_assign_expr_nested -class TestStringFormatting -TestStringFormatting.test_f_string_without_placeholders - -function: test_invalid_dot_format_calls -function: test_invalid_percent_format_calls -function: test_ok_percent_format_cannot_determine_element_count -class TestAsyncStatements -TestAsyncStatements.test_asyncDef - -function: test_asyncDefAwait -function: test_asyncDefUndefined -function: test_asyncFor -function: test_asyncForUnderscoreLoopVar -function: test_loopControlInAsyncFor -function: test_loopControlInAsyncForElse -function: test_asyncWith -function: test_asyncWithItem -function: test_matmul -function: test_formatstring -function: test_raise_notimplemented -class TestIncompatiblePrintOperator -TestIncompatiblePrintOperator.test_valid_print - -function: test_invalid_print_when_imported_from_future -function: test_print_augmented_assign -function: test_print_function_assignment -function: test_print_in_lambda -function: test_print_returned_in_function -function: test_print_as_condition_test - -@clean test_type_annotations.py -class TestTypeAnnotations -TestTypeAnnotations.test_typingOverload - -function: test_typingExtensionsOverload -function: test_typingOverloadAsync -function: test_overload_with_multiple_decorators -function: test_overload_in_class -function: test_aliased_import -function: test_not_a_typing_overload -function: test_variable_annotations -function: test_variable_annotation_references_self_name_undefined -function: test_TypeAlias_annotations -function: test_annotating_an_import -function: test_unused_annotation -function: test_unused_annotation_in_outer_scope_reassigned_in_local_scope -function: test_unassigned_annotation_is_undefined -function: test_annotated_async_def -function: test_postponed_annotations -function: test_type_annotation_clobbers_all -function: test_return_annotation_is_class_scope_variable -function: test_return_annotation_is_function_body_variable -function: test_positional_only_argument_annotations -function: test_partially_quoted_type_annotation -function: test_partially_quoted_type_assignment -function: test_nested_partially_quoted_type_assignment -function: test_quoted_type_cast -function: test_type_cast_literal_str_to_str -function: test_quoted_type_cast_renamed_import -function: test_quoted_TypeVar_constraints -function: test_quoted_TypeVar_bound -function: test_literal_type_typing -function: test_literal_type_typing_extensions -function: test_annotated_type_typing_missing_forward_type -function: test_annotated_type_typing_missing_forward_type_multiple_args -function: test_annotated_type_typing_with_string_args -function: test_annotated_type_typing_with_string_args_in_union -function: test_literal_type_some_other_module -function: test_literal_union_type_typing -function: test_deferred_twice_annotation -function: test_partial_string_annotations_with_future_annotations -function: test_forward_annotations_for_classes_in_scope -function: test_idomiatic_typing_guards -function: test_typing_guard_for_protocol -function: test_typednames_correct_forward_ref -function: test_namedtypes_classes -function: test_variadic_generics -function: test_type_statements -function: test_type_parameters_functions -function: test_type_parameters_do_not_escape_function_scopes -function: test_type_parameters_classes -function: test_type_parameters_do_not_escape_class_scopes -function: test_type_parameters_TypeVarTuple -function: test_type_parameters_ParamSpec - -@clean test_undefined_names.py -class Test -Test.test_undefined -Test.test_definedInListComp -Test.test_undefinedInListComp - -function: test_undefinedExceptionName -function: test_namesDeclaredInExceptBlocks -function: test_undefinedExceptionNameObscuringLocalVariable -function: test_undefinedExceptionNameObscuringLocalVariable2 -function: test_undefinedExceptionNameObscuringLocalVariableFalsePositive1 -function: test_delExceptionInExcept -function: test_undefinedExceptionNameObscuringLocalVariableFalsePositive2 -function: test_undefinedExceptionNameObscuringGlobalVariable -function: test_undefinedExceptionNameObscuringGlobalVariable2 -function: test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1 -function: test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2 -function: test_functionsNeedGlobalScope -function: test_builtins -function: test_builtinWindowsError -function: test_moduleAnnotations -function: test_magicGlobalsFile -function: test_magicGlobalsBuiltins -function: test_magicGlobalsName -function: test_magicGlobalsPath -function: test_magicModuleInClassScope -function: test_magicQualnameInClassScope -function: test_globalImportStar -function: test_definedByGlobal -function: test_definedByGlobalMultipleNames -function: test_globalInGlobalScope -function: test_global_reset_name_only -function: test_unused_global -function: test_del -function: test_delGlobal -function: test_delUndefined -function: test_delConditional -function: test_delConditionalNested -function: test_delWhile -function: test_delWhileTestUsage -function: test_delWhileNested -function: test_globalFromNestedScope -function: test_laterRedefinedGlobalFromNestedScope -function: test_laterRedefinedGlobalFromNestedScope2 -function: test_intermediateClassScopeIgnored -function: test_doubleNestingReportsClosestName -function: test_laterRedefinedGlobalFromNestedScope3 -function: test_undefinedAugmentedAssignment -function: test_nestedClass -function: test_badNestedClass -function: test_definedAsStarArgs -function: test_definedAsStarUnpack -function: test_usedAsStarUnpack -function: test_unusedAsStarUnpack -function: test_keywordOnlyArgs -function: test_keywordOnlyArgsUndefined -function: test_annotationUndefined -function: test_metaClassUndefined -function: test_definedInGenExp -function: test_undefinedInGenExpNested -function: test_undefinedWithErrorHandler -function: test_definedInClass -function: test_definedInClassNested -function: test_undefinedInLoop -function: test_definedFromLambdaInDictionaryComprehension -function: test_definedFromLambdaInGenerator -function: test_undefinedFromLambdaInDictionaryComprehension -function: test_undefinedFromLambdaInComprehension -function: test_dunderClass -class NameTests -NameTests.test_impossibleContext - - - - ---- changed - - - - ---- classes - - - - - ---- recent - -Found 7:in_scope - - - - - - - - - ---- not used -function: counter - - - - -Found 22:handleChildren - - - - - - - - - - - - - - - - - - - - - - - - -*** play with _FieldsOrder - ---- PR - - - - -@language python -"""Recursively import all python files in a directory and clean the result.""" -@tabwidth -4 # For a better match. -g.cls() - -# dir_ = r'C:\Python\Python3.12\Lib\site-packages\coverage' -dir_ = r'C:\Repos\ekr-fork-pyflakes' - -c.recursiveImport( - dir_=dir_, - kind = '@clean', # '@auto', '@clean', '@nosent','@file', - recursive = True, - safe_at_file = False, - # '.html', '.js', '.json', '.py', '.rs', '.svg', '.ts', '.tsx'] - # '.codon', '.cpp', '.cc', '.el', '.scm', - theTypes = ['.py',], - verbose = False, -) -if 1: - last = c.lastTopLevel() - last.expand() - if last.hasChildren(): - last.firstChild().expand() - c.redraw(last) -print('Done') - - - -vertical or horizontal - -myLeoSettings.leo: vertical - - -@language rest -@wrap - -See #3456. - - - - -# leonine - - - - - -True: (Recommended) Make a "Recovered Nodes" node whenever -Leo reads a file that has been changed outside of Leo. - - - - - - - - -'''(ekr.leo) Clean newlines from EKR text.''' -s = p.b.replace('\n\n','\nPARA\n') -s = s.replace('\n',' ') -s = s.replace(' PARA ','\n\n') -s = s.replace(' ',' ') -p.b = s - - - - - - -Set to True to enable node appearance modifications -See tree-declutter-patterns - -# **Decluttering** replaces controls custom formatting of headlines, including: - -# - Hiding or changing headline text, -# - Adding icons to headlines, -# - Changing the styling of headlines. - -# @bool tree-declutter must be True to enable decluttering. - -# blank lines and lines starting with '#' are ignored. -# See the children of this node for details. - -RULE ^@file (.*) -REPLACE \1 -ICON file_icons/file_file.png - -# **Decluttering** replaces controls custom formatting of headlines, including: - -# - Hiding or changing headline text, -# - Adding icons to headlines, -# - Changing the styling of headlines. - -# Decluttering is *inactive* when you are editing a headline. - -# Decluttering is *completely optional*. To enable decluttering, use:: - - # @bool tree-declutter = True - -# Decluttering is controlled by **decluttering rulesets**. -# You specify decluttering rulesets in the body text of:: - - # @data tree-declutter-patterns - -# As usual with @data nodes: - -# - Blank lines and lines starting with '#' are ignored. -# - You may organize the text of the @data node using child nodes. - -# Each ruleset consists of a list of lines: - -# - The first line is a **rule line**, containing a **find pattern**. -# - The second line is a **replacement line**. -# - The ruleset ends with zero or more **style lines**. - -# Find patterns are `regular expressions <https://docs.python.org/2/library/re.html>`_. -# Decluttering affects only those headlines that match a rule pattern. - -# The following section shows some example rulesets. Later sections discuss decluttering commands, patterns and styles in more detail. - -# All rulesets start with a **rule line** of the form:: - - # RULE <regular expression> - -# The ruleset matches a headline if and only if the regular expression matches. Matches can start anywhere in the headline. Leo first attempts to a match using re.match. If that doesn't work, Leo tries re.search. - -# A **replacement line** must follow the rule line. Here are the valid forms:: - - # REPLACE <substitution expression> - # REPLACE-HEAD - # REPLACE-TAIL - # REPLACE-REST - -# - REPLACE replaces the headline by the value of the substitution expression. For example:: - - # REPLACE \1 - - # matches replaces the headline by the first matched regex group. - -# - REPLACE-HEAD replaces replace the headline by the text that precedes the matched text. - -# - REPLACE-TAIL replaces the headline by the text that follows the matched text. - -# - REPLACE-REST replaces the headline by everything except the matched text. - -g.cls() - -print('===== Start =====') - -class CreateDecorators: - ''' - A class to create decorators from tables in getPublicCommands. - - Note: the node "Found: getPublicCommands" must exist. - ''' - def __init__(self,c,make_changes): - self.c = c - self.fixups = self.create_fixups() - self.n = 0 - self.n_fail = 0 - self.make_changes=make_changes - self.suppress = [ - 'c.frame.body and c.frame.body.addEditor', - 'cls','cloneFindParents','cycleTabFocus', - 'k and k.keyboardQuit', - 'menuShortcutPlaceHolder','removeBlankLines', - 'saveBuffersKillLeo', - ] - @others - -CreateDecorators(c,make_changes=False).run() - -# Leo applies style lines only if they appear in a ruleset that matches a headline. -# Style lines do the following... - -# Add an icon to the headline:: - - # ICON path/to/icon - -# Set the background or foreground color to a color number or names:: - - # BG #FF8800 - # FG @solarized-magenta - -# Set the font to a given font name:: - - # Font Times - -# Set the font size in pixels (PX) or points (PT):: - - # PX 40 - # PT 16 - -# Enable or disable italics:: - - # ITALIC 0 - # ITALIC 1 - -# Set the font weight to one of Light, Normal, DemiBold, Bold, Black:: - - # WEIGHT DemoBold - -# Add Icon to folders and remove /-/ -RULE ^/(.*)/$ -REPLACE \1 -ICON file_icons/folder.png - -# Add icon to path folders and remove @path -RULE ^@path (.*) -REPLACE \1 -ICON file_icons/folder_path.png - -# Add Icon to removed folders and remove */-/* -RULE ^\*/(.*)/\*$ -REPLACE \1 -ICON file_icons/folder_removed.png - -# Add Icon to removed files and remove *-* but not **-** -RULE ^\*([^\*/]*[^\*]*[^\*/]*)\*$ -REPLACE \1 -ICON file_icons/removed.png - -# if the node name starts with 'peacock node DEMO', make a mess of it -RULE ^(peacock node DEMO) -REPLACE LOOK: \1 -ICON Tango/16x16/emotes/face-grin.png -ICON Tango/16x16/emotes/face-wink.png -FG @solarized-magenta -BG white -FONT Times -PX 40 -ITALIC 1 -WEIGHT Bold - -# RULE :([\w_@]+:)+\s*$ -# REPLACE-HEAD - -# remove @clean etc. and use an icon -RULE ^@clean (.*) -REPLACE \1 -ICON file_icons/file_clean.png - -RULE ^@auto (.*) -REPLACE \1 -ICON file_icons/file_auto.png - -RULE ^@edit (.*) -REPLACE \1 -ICON file_icons/file_edit.png - -RULE ^@asis (.*) -REPLACE \1 -ICON file_icons/file_asis.png - -RULE ^@nosent (.*) -REPLACE \1 -ICON file_icons/file_nosent.png - -RULE ^@file (.*) -REPLACE \1 -ICON file_icons/file_file.png - -# show the last part of long filenames -RULE ^.{1,1000}/(.{20}) -REPLACE …/\1 - - - - - -def create_d(self,lines,publicCommands): - '''Create a dict. keys are method names; values are command names.''' - trace = False - if trace: - print('') - g.trace(publicCommands.h) - d = {} - for s in lines: - aList = s.split() - if len(aList) > 2: - aList = [aList[0],' '.join(aList[1:])] - c_name,f_name = aList[0].strip(),aList[1].strip() - if ' ' not in f_name: - f_name = f_name.split('.')[-1] - # if '(' in f_name: - # f_name = f_name[:f_name.find('(')] - if trace: g.trace('%45s %s' % (c_name,f_name)) - d [f_name] = c_name - return d - - - - - -Only supported with the mod_tempfname.py plugin. - -True: The plugin will store temporary files utilizing cleaner -file names (no unique number is appended to the node's headline text). -Unique temporary directory paths are used to insure unique files are -created by creating temporary directories reflecting each node's ancestor -nodes in the Leo outline. Note: Do not have multiple sibling nodes (nodes -having the same parent node) in Leo with the same headline text. There will -be a conflict if both are opened in an external editor at the same time. - -False: The plugin will store temporary files with an appended -unique number to insure unique temporary filenames. - -True: check all @<file> nodes in the outline for changes in corresponding external files. - - - - -def create_decorator(self,c_name,f_name,root): - ''' - Search root for a definition of f_name. - If found, insert @cmd(f_name) before the definition. - ''' - # g.trace('%45s %s' % (c_name,f_name)) - trace = False - found = False - decorator = "@cmd('%s')\n" % (c_name) - for p in root.self_and_subtree(): - changed,result = False,[] - for s in g.splitLines(p.b): - if g.match_word(s,0,'def ' + f_name): - if found: - if f_name not in self.suppress: - g.trace('duplicate def',f_name) - else: - changed,found = True,True - result.append(decorator) - # print('%s%s' % (decorator,s)) - result.append(s) - # if changed and self.make_changes: - # new_body = ''.join(result) - # # use git as our undo :-) - # p.b = new_body - return found - - -It is *strange* to set this to True! -@language rest - -To test #2041 & #2094 - -The @bool use-find-dialog and @bool minibuffer-find-mode settings comprise -a tri-state setting, as shown in this table: - -minibuffer-find-mode use-find-dialog mode: Ctrl-F puts focus in --------------------- --------------- -------------------------- - True Ignored minibuffer - False True dialog - False False Find tab in the log pane - -*All modes* - -- Start the search with Ctrl-F (start-search). -- Enter the find pattern. -- (Optional) Use <Tab> to enter the search pattern. -- Use <Enter> to start the search. - -*dialog and find tab modes* - -- Non-functional "buttons" remind you of key bindings. - -*minibuffer mode* - -- Use Ctrl-G as always to leave the minibuffer. -- The Find tab is not made visible, but the status area shows the settings. -@language rest - -The @bool use-find-dialog and @bool minibuffer-find-mode settings comprise -a tri-state setting, as shown in this table: - -minibuffer-find-mode use-find-dialog mode: Ctrl-F puts focus in --------------------- --------------- -------------------------- - True Ignored minibuffer - False True dialog - False False Find tab in the log pane - -*All modes* - -- Start the seas with Ctrl-F (start-search). -- Enter the find pattern. -- (Optional) Use <Tab> to enter the search pattern. -- Use <Enter> to start the search. - -*dialog and find tab modes* - -- Non-functional "buttons" remind you of key bindings. - -*minibuffer mode* - -- Use Ctrl-G as always to leave the minibuffer. -- The Find tab is not made visible, but the status area shows the settings. -Added on-popover to import-html-tags (for leovue) -# lowercase html tags, one per line. -# *** Add ons-popover tag for LeoVue. - -a -abbr -acronym -address -applet -area -b -base -basefont -bdo -big -blockquote -body -br -button -caption -center -cite -code -col -colgroup -dd -del -dfn -dir -div -dl -dt -em -fieldset -font -form -frame -frameset -head -h1 -h2 -h3 -h4 -h5 -h6 -hr -html -i -iframe -img -input -ins -kbd -label -legend -li -link -map -menu -meta -noframes -noscript -object -ol -ons-popover -optgroup -option -p -param -pre -q -s -samp -script -select -small -span -strike -strong -style -sub -sup -table -tbody -td -textarea -tfoot -th -thead -title -tr -tt -u -ul -var -# lowercase xml tags, one per line. - -html -body -head -div -table - -For make-stub-files -True: allow stub files to be overwritten - - -def create_decorators(self,d,root): - '''Create decorators for all items in d in root's tree.''' - # print('***** %s' % root.h) - if root.h in self.fixups: - roots = [] - aList = self.fixups.get(root.h) - for root2_h in aList: - root2 = g.findNodeAnywhere(self.c,root2_h) - if root2: - # g.trace(root.h,'=====>',root2.h) - roots.append(root2) - else: - g.trace('===== not found',root2_h) - else: - roots = [root] - for f_name in sorted(d.keys()): - found = False - for root in roots: - c_name = d.get(f_name) - found = self.create_decorator(c_name,f_name,root) - if found: break - if not found and f_name not in self.suppress: - print('===== not found: %30s %s' % (root.h,f_name)) - self.n_fail += 1 - - - - - -AstFormatter.*: str -Pattern.all_matches: Sequence -Pattern.full_balanced_match: Optional[int] -Pattern.match_balanced: int -Pattern.match_entire_string: bool -StandAloneMakeStubFile.scan_types: Dict[str, str] -StubFormatter.do_.*: str -StubTraverser.format_returns: str -StubTraverser.match_return_patterns: Tuple[bool,str] -StubTraverser.match_return_pattern: Optional[str] -StubTraverser.match_balanced: int -# Patterns to be applied to argument lists and return expressions. - -aList: Sequence -aList1: Sequence -aList2: Sequence -c: C -c1: C -c2: C -i: int -j: int -k: int -node: ast.Ast -p: P -p1: P -p2: P -s: str -s2: str -v: V -v1: V -v2: V - -aList: Sequence -controller: StandAloneMakeStubFile -fn: str -i[0-3]*: int -parser: optparse.OptionParser -node: Node -s[0-3]*: str -strict: bool - -repr(*): str -str.join(*): str -str.replace(*): str -str%(*): str -str%str: str - -.*__name__: str - -# Lines to be inserted at the start of each stub file. - -from typing import Any, Dict, Optional, Sequence, Tuple, Union -# At present, I don't understand how to tell mypy about ast.Node -# import ast -# Node = ast.Node - -Node = Any - -The directory to which stub files are written. - -# Recommended plugins, from leoSettings.leo: - -plugins_menu.py -mod_scripting.py -nav_qt.py -viewrendered.py - -# bookmarks.py -# contextmenu.py # Required by the vim.py and xemacs.py plugins. -# nodetags.py -# quicksearch.py -# todo.py -# viewrendered3.py - -### Testing - -# backlink.py -# freewin.py -# mod_autosave.py -# quickMove.py -# screenshots.py -# settings_finder.py -# rpcalc.py -# wikiview.py - -def create_fixups(self): - ''' - Return a fixup dict. - Keys are headlines for classes. - Values are new headlines of nodes containing the actual class. - ''' - return { - 'ChapterCommandsClass': ['class ChapterController'], - 'EditCommandsClass': [ - 'EditCommandsClass', - 'class Commands', - 'class LeoQtFrame', - 'class LeoBody', - ], - 'class SearchCommandsClass': ['class LeoFind (LeoFind.py)'], - 'KeyHandlerCommandsClass (add docstrings)': [ - 'class KeyHandlerClass', - 'class AutoCompleterClass', - ] - } - - - - - - -# True: show vr pane when opening a file. -# True: hide the vr pane for text-only renderings. - - -def find_class(self,p): - '''Return the position of the class enclosing p.''' - for p2 in p.parents(): - if p2.h.lower().find('class') > -1 and p2.b.find('class') > -1: - return p2 - else: - g.trace('*** no class for p.h') - return None - -# regex patterns for text to be hidden by the wikiview plugin -# Blanks lines and lines starting with '#' are comment lines. - -# Each non-comment line represents a pattern. -# Use \b# for patterns starting with '#' -# Only NON `groups` parts of the pattern in parentheses will be shown. -# The first character of the pattern (not counting \b) is the leadin character. -# The pattern will be applied only for strings starting with the leadin character. - -# UNLs - -\bunl:(//.*#.*-->).* -\bunl:(//.*#).* -\bunl:(//.*-->).* -\bunl:(//).* -\bfile:(//.*-->).* -\bhttps?:(//.*-->).* -## \bfile:(//.*-->)\S+\b -## \bhttps?:(//.*-->)\S+\b - -# regular urls - -\bhttps?:(//.*/)\w+\b -\bfile:(//.*/)\w+\b - -# restructuredText `Visible text <http://invisible.url/here>` - -(`)\S+(\s*<https?://\S+>`_) -(`)\S+(\s*<file://\S+>`_) - -# Test patterns: see http://pythex.org/ - -# unl://leoSettings.leo#@settings-->Plugins-->wikiview plugin -# unl://ekr.leo#Startup-->@settings-->@@data global-abbreviations -# unl://#Startup-->@settings-->@@data global-abbreviations -# unl://Startup-->@settings-->@@data global-abbreviations -# file://Startup-->@settings -# file://#some-->headlines-->mynode -# http://#some-->headlines-->mynode -# https://#some-->headlines-->mynode -# http://www.google.com/search -# https://www.google.com/search -# file://www.google.com/search -# `Python <https://www.python.org/>`_ -# `Python <file://www.python.org/>`_ - -Should wikiview mode be active by default? - - - - - - - -Only difference from myLeoSettings.leo - -Note: EKRWinowsDark.leo defines comment1_font - -All three @color settings work. -The @font setting does not work. - -def find_next_clone(self,p): - v = p.v - p = p.copy() - p.moveToThreadNext() - wrapped = False - while 1: - # g.trace(p.v,p.h) - if p and p.v == v: - break - elif p: - p.moveToThreadNext() - elif wrapped: - break - else: - wrapped = True - p = c.rootPosition() - return p - -Bold -Italics - -# bold keywords defined in forth-bold-words - -# Note: the default font size is 12. -rest_comment1_family = None -rest_comment1_size = 12pt -rest_comment1_slant = italic -rest_comment1_weight = None - -These must be @string settings, even though they do affect colors. -solarized blue: #268bd2 -solarized-red = #dc322f - -@nosearch - -def munge_lines(self,root,publicCommands): - '''Return munged lines of ''' - # print('') - # g.trace(root.h) - s = publicCommands.b - i,j = s.find('{'),s.find('}') - s = s[i+1:j] - # print(s) - lines = sorted([z.strip() for z in g.splitLines(s) if z.strip()]) - lines = [z for z in lines if not z.startswith('#')] - lines = [z[:z.find('#')] if z.find('#') > -1 else z for z in lines] - lines = [z.rstrip().rstrip(',') for z in lines] - lines = [z[1:] for z in lines] - lines = [z.replace("':",' ') for z in lines] - # print('\n'.join(lines)) - self.n += len(lines) - return lines - -solarized blue: #268bd2 -# Note: Use jj instead of escape to end insert mode. - -""" -Back up this .leo file. - -os.environ['LEO_BACKUP'] must be the path to an existing (writable) directory. -""" -c.backup_helper(sub_dir='ekr-pyflakes') - -print(p.gnx) - -def run(self): - '''Top-level code.''' - self.n = 0 - found = g.findNodeAnywhere(c,'Found: getPublicCommands') - assert found - for child in found.children(): - publicCommands = self.find_next_clone(child) - root = self.find_class(publicCommands) - if root: - lines = self.munge_lines(root,publicCommands) - d = self.create_d(lines,publicCommands) - self.create_decorators(d,root) - print('\n%s commands %s failed' % (self.n,self.n_fail)) - -@language python -"""Introspect""" - -# By Terry Brown. Requires Python 2.x. - -# https://groups.google.com/forum/#!msg/leo-editor/Qu2HccpC_wc/_ee11jIvAQAJ - -import types - -sub_mode = 'instance' -# 'instance' or 'class' - controls which, instance or class names, -# are put it a subnode. 'instance class' sub-nodes both. -# '' appends classes after names, not useful. - -def classname(thing): - if hasattr(thing, '__class__'): - return thing.__class__.__name__ - else: - return thing.__name__ - -if not hasattr(c.p.v, '_introspection_target'): - txt = g.app.gui.runAskOkCancelStringDialog( - c, "Introspect what", "Introspect what") - if txt is not None: - o = eval(txt) - c.p.v._introspection_target = o - c.p.h = "%s %s" % (txt, classname(o)) - -# c.p.deletePositionsInList([i.copy() for i in p.children()]) - -obj = c.p.v._introspection_target -g.es(classname(obj)) - -def show_obj(c, obj): - - inames = sorted(dir(obj)) - - things = {} - instances = [] - for iname in inames: - - if iname.startswith('__'): - continue - - o = getattr(obj, iname) - cname = classname(o) - instances.append((iname, o)) - things.setdefault(cname, []).append(instances[-1]) - - if 'instance' in sub_mode: - tnd = c.p.v.insertAsNthChild(0) - tnd.h = "<by name>" - else: - tnd = c.p.v - - instances.sort() - for iname, o in instances: - - if classname(o) == 'position': - # apparently this collapses the space-time continuum? - continue - - nd = tnd.insertAsLastChild() - - if not seen_already(tnd, nd, iname, o): - nd.h = "%s %s" % (iname, format_type(nd, o)) - nd._introspection_target = o - - if 'class' in sub_mode: - ttnd = c.p.v.insertAsNthChild(0) - ttnd.h = "<by class>" - else: - ttnd = c.p.v - - for cname in sorted(things): - - if len(things[cname]) == 1: - tnd = ttnd - else: - tnd = ttnd.insertAsLastChild() - tnd.h = "<%s>"%cname - - for iname, o in sorted(things[cname]): - - if cname == 'position': - # apparently this collapses the space-time continuum? - continue - - nd = tnd.insertAsLastChild() - if not seen_already(tnd, nd, iname, o): - show_child(nd, iname, o) - nd._introspection_target = o - -def seen_already(tnd, nd, iname, o): - - up = tnd.parents - while up: - if (hasattr(up[0], '_introspection_target') and - up[0]._introspection_target is o): - break - up = up[0].parents - else: - return False - - nd.h = "[%s %s]" % (classname(o), iname) - pos = c.vnode2position(up[0]) - nd.b = pos.get_UNL(with_file=True, with_proto=True) - - return True - -def show_child(nd, iname, o): - - nd._introspection_target = o - nd.h = "%s %s" % (format_type(nd, o), iname) - -docable = ( - types.ClassType, types.MethodType, types.UnboundMethodType, - types.BuiltinFunctionType, types.BuiltinMethodType, -) - -def format_type(nd, o): - - if isinstance(o, docable): - if hasattr(o, '__doc__'): - nd.b = o.__doc__ - - if isinstance(o, (str, unicode)): - nd.b = o - return "%s '%s'" % (classname(o), o[:20]) - elif isinstance(o, bool): - return "%s %s" % (classname(o), 'T' if o else 'F') - elif isinstance(o, (int, float)): - return "%s %s" % (classname(o), o) - elif isinstance(o, (tuple, list, dict)): - return "%s %s" % (classname(o), len(o)) - else: - return classname(o) - -def show_list(c, list_): - - if len(list_) > 100: - nd = c.p.v.insertAsLastChild() - nd.h = "<%s of %d items truncated>" % len(list_.__class__.__name__, list_) - - if len(list_) == 0: - nd = c.p.v.insertAsLastChild() - nd.h = "<%s of 0 items>" % list_.__class__.__name__ - - for n, i in enumerate(list_[:100]): - nd = c.p.v.insertAsLastChild() - show_child(nd, '', i) - nd.h = "%d: %s" % (n, nd.h) - nd._introspection_target = i - -def show_dict(c, dict_): - - if len(dict_) > 100: - nd = c.p.v.insertAsLastChild() - nd.h = "<dict of %d items truncated>" % len(dict_) - - if len(dict_) == 0: - nd = c.p.v.insertAsLastChild() - nd.h = "<dict of 0 items>" - - keys = dict_.keys() - keys.sort() - - for k in keys[:100]: - nd = c.p.v.insertAsLastChild() - i = dict_[k] - show_child(nd, '', i) - nd.h = "%s: %s" % (k, nd.h) - nd._introspection_target = i - -dispatch = { - list: show_list, - tuple: show_list, - dict: show_dict, -} - -func = dispatch.get(type(obj), show_obj) - -func(c, obj) - -c.p.expand() -c.redraw() - -'''@button (ekr.leo) Join the lines, with ; separators''' -# No longer needed on Windows 10 -w = c.frame.body.widget -aList = [z.rstrip() for z in p.b.split('\n')] -p.b = ';'.join(aList) -c.bodyWantsFocusNow() - -g.cls() - -# Changed files: -# leoApp.py -# leoAtFile.py -# leoCommands.py -# leoFileCommands.py -# leoFrame.py -# leoUndo.py -# qt_frame.py - -make_changes = True - # True, actually make the change - -class CreateDecorators: - ''' - A class to create decorators from tables in getPublicCommands. - - Note: the node "Found: getPublicCommands" must exist. - ''' - def __init__(self): - self.n = 0 - self.n_fail = 0 - self.s = self.define_s() - @others - -CreateDecorators().run() - -def create_d(self,lines): - '''Create a dict. keys are method names; values are command names.''' - trace = False - d = {} - for s in lines: - aList = s.split() - if len(aList) > 2: - aList = [aList[0],' '.join(aList[1:])] - c_name,f_name = aList[0].strip(),aList[1].strip() - if ' ' not in f_name: - f_name = f_name.split('.')[-1] - # if '(' in f_name: - # f_name = f_name[:f_name.find('(')] - if trace: g.trace('%45s %s' % (c_name,f_name)) - d [f_name] = c_name - return d - -h = '--- @edit files' -root = g.findTopLevelNode(c, h) -start_s = 'LilyPond is free software:' -end_s = 'If not, see <http://www.gnu.org/licenses/>.' -for p in root.children(): - s = p.b - i = s.find(start_s) - j = s.find(end_s) - if -1 < i < j: - s = s[:i] + s[j + len(end_s):] - p.b = s - else: - print('not found', p.h) -print('done') -def create_decorator(self,c_name,f_name,root): - ''' - Search root for a definition of f_name. - If found, insert @cmd(f_name) before the definition. - ''' - trace = True - found = False - decorator = "@cmd('%s')\n" % (c_name) - for p in root.self_and_subtree(): - changed,result = False,[] - for s in g.splitLines(p.b): - if g.match_word(s,0,'def ' + f_name): - if found: - if f_name not in self.suppress: - g.trace('duplicate def',f_name) - else: - changed,found = True,True - result.append(decorator) - # print('%s%s' % (decorator,s)) - result.append(s) - if changed and make_changes: - new_body = ''.join(result) - print('%40s %s' % (p.h[:40],decorator.rstrip())) - # use git as our undo :-) - # p.b = new_body - return found - -def create_decorators(self,d): ### ,root): - '''Create decorators for all items in d in root's tree.''' - table = ( - 'class Commands', # c. - 'class LeoQtFrame', # f. - 'class LeoFrame', # f. - 'class LeoApp', # g.app. - '@file leoAtFile.py', # c.atFileCommands - '@file leoFileCommands.py', # c.fileCommands - 'class Undoer', # c.undoer - ) - roots = [] - for h in table: - root = g.findNodeAnywhere(c,h) - assert root,h - roots.append(root) - for f_name in sorted(d.keys()): - found = False - for root in roots: - c_name = d.get(f_name) - found = self.create_decorator(c_name,f_name,root) - if found: break - if not found and f_name not in self.suppress: - print('===== not found: %s' % (f_name)) - self.n_fail += 1 - -# 'check-all-python-code': c.checkAllPythonCode, -# 'check-python-code': c.checkPythonCode, -# 'extract-python-method': c.extractPythonMethod, -# 'extract-section': c.extractSection, -# 'import-at-file': c.importAtFile, -# 'import-at-root': c.importAtRoot, -# 'import-cweb-files': c.importCWEBFiles, -# 'import-derived-file': c.importDerivedFile, -# 'import-flattened-outline': c.importFlattenedOutline, -# 'import-noweb-files': c.importNowebFiles, -# 'mark-changed-roots': c.markChangedRoots, -# 'mark-clones': c.markClones, -# 'open-compare-window': c.openCompareWindow, -# 'open-online-tutorial': c.leoTutorial, -# 'reformat-body': c.reformatBody, # 2013/10/02. -def define_s(self): - return ''' -'abort-edit-headline': f.abortEditLabelCommand, -'about-leo': c.about, -'add-comments': c.addComments, -'beautify': c.beautifyPythonCode, -'beautify-all': c.beautifyAllPythonCode, -'beautify-c': c.beautifyCCode, -'beautify-tree': c.beautifyPythonTree, -'cascade-windows': f.cascade, -'check-derived-file': c.atFileCommands.checkDerivedFile, -'check-leo-file': c.fileCommands.checkLeoFile, -'check-outline': c.fullCheckOutline, -'clean-recent-files': c.cleanRecentFiles, -'clear-recent-files': c.clearRecentFiles, -'clone-node': c.clone, -'clone-node-to-last-node': c.cloneToLastNode, -'close-window': c.close, -'contract-all': c.contractAllHeadlines, -'contract-all-other-nodes': c.contractAllOtherNodes, -'contract-node': c.contractNode, -'contract-or-go-left': c.contractNodeOrGoToParent, -'contract-parent': c.contractParent, -'convert-all-blanks': c.convertAllBlanks, -'convert-all-tabs': c.convertAllTabs, -'convert-blanks': c.convertBlanks, -'convert-tabs': c.convertTabs, -'copy-node': c.copyOutline, -'copy-text': f.copyText, -'cut-node': c.cutOutline, -'cut-text': f.cutText, -'de-hoist': c.dehoist, -'delete-comments': c.deleteComments, -'delete-node': c.deleteOutline, -'demote': c.demote, -'dump-outline': c.dumpOutline, -'edit-headline': c.editHeadline, -'end-edit-headline': f.endEditLabelCommand, -'equal-sized-panes': f.equalSizedPanes, -'execute-script': c.executeScript, -'exit-leo': g.app.onQuit, -'expand-all': c.expandAllHeadlines, -'expand-all-subheads': c.expandAllSubheads, -'expand-ancestors-only': c.expandOnlyAncestorsOfNode, -'expand-and-go-right': c.expandNodeAndGoToFirstChild, -'expand-next-level': c.expandNextLevel, -'expand-node': c.expandNode, -'expand-or-go-right': c.expandNodeOrGoToFirstChild, -'expand-prev-level': c.expandPrevLevel, -'expand-to-level-1': c.expandLevel1, -'expand-to-level-2': c.expandLevel2, -'expand-to-level-3': c.expandLevel3, -'expand-to-level-4': c.expandLevel4, -'expand-to-level-5': c.expandLevel5, -'expand-to-level-6': c.expandLevel6, -'expand-to-level-7': c.expandLevel7, -'expand-to-level-8': c.expandLevel8, -'expand-to-level-9': c.expandLevel9, -'export-headlines': c.exportHeadlines, -'extract': c.extract, -'extract-names': c.extractSectionNames, -'find-next-clone': c.findNextClone, -'flatten-outline': c.flattenOutline, -'flatten-outline-to-node': c.flattenOutlineToNode, -'go-back': c.goPrevVisitedNode, -'go-forward': c.goNextVisitedNode, -'goto-first-node': c.goToFirstNode, -'goto-first-sibling': c.goToFirstSibling, -'goto-first-visible-node': c.goToFirstVisibleNode, -'goto-last-node': c.goToLastNode, -'goto-last-sibling': c.goToLastSibling, -'goto-last-visible-node': c.goToLastVisibleNode, -'goto-next-changed': c.goToNextDirtyHeadline, -'goto-next-clone': c.goToNextClone, -'goto-next-history-node': c.goToNextHistory, -'goto-next-marked': c.goToNextMarkedHeadline, -'goto-next-node': c.selectThreadNext, -'goto-next-sibling': c.goToNextSibling, -'goto-next-visible': c.selectVisNext, -'goto-parent': c.goToParent, -'goto-prev-history-node': c.goToPrevHistory, -'goto-prev-node': c.selectThreadBack, -'goto-prev-sibling': c.goToPrevSibling, -'goto-prev-visible': c.selectVisBack, -'hide-invisibles': c.hideInvisibles, -'hoist': c.hoist, -'import-file': c.importAnyFile, -'indent-region': c.indentBody, -'insert-body-time': c.insertBodyTime, -'insert-child': c.insertChild, -'insert-node': c.insertHeadline, -'insert-node-before': c.insertHeadlineBefore, -'mark': c.markHeadline, -'mark-changed-items': c.markChangedHeadlines, -'mark-subheads': c.markSubheads, -'match-brackets': c.findMatchingBracket, -'minimize-all': f.minimizeAll, -'move-outline-down': c.moveOutlineDown, -'move-outline-left': c.moveOutlineLeft, -'move-outline-right': c.moveOutlineRight, -'move-outline-up': c.moveOutlineUp, -'new': c.new, -'open-cheat-sheet-leo': c.openCheatSheet, -'open-leoDocs-leo': c.leoDocumentation, -'open-leoPlugins-leo': c.openLeoPlugins, -'open-leoSettings-leo': c.openLeoSettings, -'open-local-settings': c.selectAtSettingsNode, -'open-myLeoSettings-leo': c.openMyLeoSettings, -'open-offline-tutorial': f.leoHelp, -'open-online-home': c.leoHome, -'open-online-toc': c.openLeoTOC, -'open-online-tutorials': c.openLeoTutorials, -'open-online-videos': c.openLeoVideos, -'open-outline': c.open, -'open-python-window': c.openPythonWindow, -'open-quickstart-leo': c.leoQuickStart, -'open-scripts-leo': c.openLeoScripts, -'open-users-guide': c.openLeoUsersGuide, -'open-with': c.openWith, -'outline-to-cweb': c.outlineToCWEB, -'outline-to-noweb': c.outlineToNoweb, -'paste-node': c.pasteOutline, -'paste-retaining-clones': c.pasteOutlineRetainingClones, -'paste-text': f.pasteText, -'pretty-print-all-python-code': c.prettyPrintAllPythonCode, -'pretty-print-python-code': c.prettyPrintPythonCode, -'promote': c.promote, -'read-at-auto-nodes': c.readAtAutoNodes, -'read-at-file-nodes': c.readAtFileNodes, -'read-at-shadow-nodes': c.readAtShadowNodes, -'read-file-into-node': c.readFileIntoNode, -'read-outline-only': c.readOutlineOnly, -'redo': c.undoer.redo, -'reformat-paragraph': c.reformatParagraph, -'refresh-from-disk': c.refreshFromDisk, -'remove-sentinels': c.removeSentinels, -'resize-to-screen': f.resizeToScreen, -'revert': c.revert, -'save-all': c.saveAll, -'save-file': c.save, -'save-file-as': c.saveAs, -'save-file-as-unzipped': c.saveAsUnzipped, -'save-file-as-zipped': c.saveAsZipped, -'save-file-to': c.saveTo, -'set-colors': c.colorPanel, -'set-font': c.fontPanel, -'settings': c.preferences, -'show-invisibles': c.showInvisibles, -'sort-children': c.sortChildren, -'sort-recent-files': c.sortRecentFiles, -'sort-siblings': c.sortSiblings, -'tangle': c.tangle, -'tangle-all': c.tangleAll, -'tangle-marked': c.tangleMarked, -'toggle-active-pane': f.toggleActivePane, -'toggle-angle-brackets': c.toggleAngleBrackets, -'toggle-invisibles': c.toggleShowInvisibles, -'toggle-sparse-move': c.toggleSparseMove, -'toggle-split-direction': f.toggleSplitDirection, -'undo': c.undoer.undo, -'unformat-paragraph': c.unformatParagraph, -'unindent-region': c.dedentBody, -'unmark-all': c.unmarkAll, -'untangle': c.untangle, -'untangle-all': c.untangleAll, -'untangle-marked': c.untangleMarked, -'weave': c.weave, -'write-at-auto-nodes': c.atFileCommands.writeAtAutoNodes, -'write-at-file-nodes': c.fileCommands.writeAtFileNodes, -'write-at-shadow-nodes': c.fileCommands.writeAtShadowNodes, -'write-dirty-at-auto-nodes': c.atFileCommands.writeDirtyAtAutoNodes, -'write-dirty-at-file-nodes': c.fileCommands.writeDirtyAtFileNodes, -'write-dirty-at-shadow-nodes': c.fileCommands.writeDirtyAtShadowNodes, -'write-file-from-node': c.writeFileFromNode, -'write-missing-at-file-nodes': c.fileCommands.writeMissingAtFileNodes, -'write-outline-only': c.fileCommands.writeOutlineOnly, -''' - -def munge_lines(self,s): - '''Return munged lines of s. ''' - lines = sorted([z.strip() for z in g.splitLines(s) if z.strip()]) - lines = [z for z in lines if not z.startswith('#')] - lines = [z[:z.find('#')] if z.find('#') > -1 else z for z in lines] - lines = [z.rstrip().rstrip(',') for z in lines] - lines = [z[1:] for z in lines] - lines = [z.replace("':",' ') for z in lines] - self.n += len(lines) - return lines - -def run(self): - '''Top-level code.''' - lines = self.munge_lines(self.s) - d = self.create_d(lines) - self.create_decorators(d) - print('%s commands %s failed' % (self.n,self.n_fail)) - -'''@button (ekr.leo) print the gnx.''' -# g.cls() -print('timestamp: %s lastIndex: %s' % (g.app.nodeIndices.timeString,g.app.nodeIndices.lastIndex)) -print('gnxs: -----') -for p in c.p.self_and_subtree(): - print('%s %s' % (p.v.gnx,p.h)) -print('uAs: -----') -for p in c.p.self_and_subtree(): - if p.v.u: - print('%s %s' % (p.v.u,p.h)) -# print('done') - -c.k.simulateCommand('print-style-sheet') -d = p.v.u -if d: - for key in sorted(d): - print('%10s %s' % (key, d.get(key))) -print(p.h) -assert False -print('hi: %s' % p.h) -'''@button (ekr.leo) set the ua to 'test-head' ''' - -p.v.u = {'test-head':p.h} -p.setDirty() -c.setChanged(True) -c.redraw() - -'''@button (ekr.leo) Split the body text at semicolons''' -# No longer needed on Windows 10 -w = c.frame.body.widget -p.b = '\n'.join(p.b.split(';')) -c.bodyWantsFocusNow() -# w.selectAllText() - -c.testManager.runTimerOnNode(p,count=100000) - -g.app.debug_app = not g.app.debug_app -g.app.debug_widgets = not g.app.debug_widgets -print('g.app.debug_app: %s' % g.app.debug_app) -print('g.app.debug_widgets: %s' % g.app.debug_widgets) -g.cls() - -import unittest -# import pyflakes -# print(pyflakes) -import os -import sys -path = g.os_path_finalize_join(os.curdir, '..') -print(path) -if 0: - import importlib - import importlib.util - # importlib.invalidate_caches() - pyflakes = importlib.util.spec_from_file_location("pyflakes", path) - # importlib.import_module('pyflakes', path) - -else: - if 'pyflakes' in sys.modules: - del sys.modules['pyflakes'] - if sys.path[0] != path: - sys.path.insert(0, path) - import pyflakes -print(pyflakes) -if 1: - from pyflakes.test import test_api, test_doctests, test_imports, test_other - from pyflakes.test import test_return_with_arguments_inside_generator - from pyflakes.test import test_undefined_names - tests = ( - test_api, - test_doctests, - test_imports, test_other, - test_return_with_arguments_inside_generator, - test_undefined_names, - ) - loader = unittest.TestLoader() - suite = unittest.TestSuite() - for module in tests: - suite.addTest(loader.loadTestsFromModule(module)) - unittest.TextTestRunner(verbosity=1).run(suite) -print('Test') -d = { -'annotate': {'priority': 2, 'prisetdate': '2017-10-22'} - 'icons': [{'yoffset': 0, - 'where': 'beforeHeadline', - 'file': 'C:\\leo.repo\\leo-editor\\leo\\Icons\\cleo\\pri2.png', - 'type': 'file', - 'xoffset': 2, - 'relPath': 'cleo\\pri2.png', - 'on': 'vnode', - 'xpad': 1, - 'cleoIcon': '1'}] -} - -g.printDict(p.u) - -print(c.p.numberOfChildren()) - - - - - -# This node contains the commands needed to execute a program in a particular language. - -# Format: language-name: command - -# Create a temporary file if c.p is not any kind of @<file> node. - -# Compute the final command as follows: - -# 1. If command contains <FILE>, replace <FILE> with the full path to the external file. -# 2. If command contains <NO-FILE>, just remove <NO-FILE>. -# 3. Otherwise, append the full path to the external file to the command. - -go: go run . <NO-FILE> -python: python -rust: rustc - -# This node contains the regex pattern to determine the line number in error messages. -# Format: language-name: regex pattern -# -# Patterns must define two groups, in either order: -# One group, containing only digits, defines the line number. -# The other group defines the file name. - -go: ^\s*(.*):([0-9]+):([0-9]+):.+$ -python: ^\s*File "(.+)", line ([0-9]+), in .+$ -rust: ^\s*--> (.+):([0-9]+):([0-9]+)\s*$ -clone-to-at-spot -restart-leo -execute-script -backup - -# import-to-indented-lisp -# import-to-indented-typescript -# import-to-indented-c - -# refresh-from-disk -# pylint -# show-plugin-handlers -# merge-node-with-next-node -# merge-node-with-prev-node -# beautify-files - - - - - - - - - - -g.cls() -print('rclick hi: %s' % c.p.h) -print('dir()', dir()) -print(script_args) -print(script_gnx) - - -# The headline must be: @outline-data tree-abbreviations - -# A list tree abbreviation names. - -# For each abbreviation name, there should be corresponding child node, -# the **abbreviation node** whose headline matches the abbreviation name. - -# When a tree abbreviation fires, Leo pastes all the descendants of -# the abbreviation node as the last children of the presently selected node. - -importer;; -per-commander-plugin;; -demo;; - - - - -''' -A template for demonstrations based on plugins/demo.py. -The demo;; abbreviation will create this tree. -''' -<< imports >> -@others -# Use the *same* command/key binding for demo-start and demo.next. -try: - if getattr(g.app, 'demo', None): - g.app.demo.next() - else: - g.cls() - print('starting demo') - demo = MyDemo(c, trace=False) - demo.bind('callout', callout) - demo.bind('title', title) - demo.start(script_string=script_string) -except Exception: - g.app.demo = None - raise - -if c.isChanged(): c.save() -import imp -from leo.core.leoQt import QtGui -import leo.plugins.demo as demo_module -imp.reload(demo_module) -# A short example. Change as needed. -script_string = '''\ -callout('Callout 1 centered') -title('This is title 1') -### -callout('Callout 2 (700, 200)', position=[700, 200]) -title('This is title 2') -demo.next() -''' - -class MyDemo (demo_module.Demo): - - def setup_script(self): - '''Delete all previously shown widgets.''' - self.delete_widgets() -def callout(text, **keys): - w = demo_module.Callout(text, **keys) - -def title(text, **keys): - w = demo_module.Title(text, **keys) - - -<< docstring >> -### From leoSettings.leo -# Created 2017/05/30 -@language python -@tabwidth -4 -__version__ = '0.0' -<< version history >> -<< imports >> -@others -''' -<|docstring|> -''' - -@ -Put notes about each version here. -<|Initial version notes|> -import leo.core.leoGlobals as g - -<|imports|> -def init (): - - ok = g.app.gui.guiName() in ('qt','qttabs') - if ok: - if 1: # Create the commander class *before* the frame is created. - g.registerHandler('before-create-leo-frame',onCreate) - else: # Create the commander class *after* the frame is created. - g.registerHandler('after-create-leo-frame',onCreate) - g.plugin_signon(__name__) - return ok - -def onCreate (tag, keys): - - c = keys.get('c') - if c: - thePluginController = pluginController(c) - -class <|Controller Class Name|>: - - @others -def __init__ (self,c): - - self.c = c - # Warning: hook handlers must use keywords.get('c'), NOT self.c. - <|ivars|> - -''' -The @auto importer for the {|{x=get_language()}|} language. - -Created {|{x=time.strftime("%Y/%m/%d")}|} by the `importer;;` abbreviation. -''' -import leo.plugins.importers.linescanner as linescanner -Importer = linescanner.Importer -@others -importer_dict = { - 'class': {|{x=cap_name}|}_Importer, - 'extensions': [<|comma-separated lists of extensions|>], - # Example: ['.c', '.cc', '.c++', '.cpp', '.cxx', '.h', '.h++'] -} -@language python -@tabwidth -4 - - - -class {|{x=cap_name}|}_Importer(Importer): - '''The importer for the {|{x=name}|} language.''' - - def __init__(self, importCommands): - '''{|{x=cap_name}|}_Importer.__init__''' - # Init the base class. - Importer.__init__(self, - importCommands, - language = '{|{x=name}|}', - state_class = {|{x=cap_name}|}_ScanState, - strict = <|True leading whitespace is significant. Otherwise False|>, - ) - - @others - -# These can be overridden in subclasses. - -### define an override if desired... - -if 0: # The base class - def clean_headline(self, s): - '''Return a cleaned up headline s.''' - return s.strip() - -# A more complex example, for the C language. - -# def clean_headline(self, s): - # '''Return a cleaned up headline s.''' - # import re - # type1 = r'(static|extern)*' - # type2 = r'(void|int|float|double|char)*' - # class_pattern = r'\s*(%s)\s*class\s+(\w+)' % (type1) - # pattern = r'\s*(%s)\s*(%s)\s*(\w+)' % (type1, type2) - # m = re.match(class_pattern, s) - # if m: - # prefix1 = '%s ' % (m.group(1)) if m.group(1) else '' - # return '%sclass %s' % (prefix1, m.group(2)) - # m = re.match(pattern, s) - # if m: - # prefix1 = '%s ' % (m.group(1)) if m.group(1) else '' - # prefix2 = '%s ' % (m.group(2)) if m.group(2) else '' - # h = m.group(3) or '<no c function name>' - # return '%s%s%s' % (prefix1, prefix2, h) - # else: - # return s - -def clean_nodes(self, parent): - ''' - Clean all nodes in parent's tree. - Subclasses override this as desired. - See perl_i.clean_nodes for an example. - ''' - pass - -class {|{x=cap_name}|}_ScanState: - '''A class representing the state of the {|{x=name}|} line-oriented scan.''' - - def __init__(self, d=None): - '''{|{x=cap_name}|}_ScanState.__init__''' - if d: - prev = d.get('prev') - self.context = prev.context - ### Adjust these by hand. - self.curlies = prev.curlies - else: - self.context = '' - ### Adjust these by hand. - self.curlies = 0 - - def __repr__(self): - '''{|{x=cap_name}|}_ScanState.__repr__''' - ### Adjust these by hand. - return "{|{x=cap_name}|}_ScanState context: %r curlies: %s" % ( - self.context, self.curlies) - - __str__ = __repr__ - - @others - - -def level(self): - '''{|{x=cap_name}|}_ScanState.level.''' - return <|self.curlies|> - ### Examples: - # self.indent # for python, coffeescript. - # self.curlies - # (self, curlies, self.parens) - -@language rest -@wrap - -The @settings tree contains all active settings. - -Settings outside this tree have no effect. -def update(self, data): - ''' - {|{x=cap_name}|}_ScanState.update - - Update the state using the 6-tuple returned by v2_scan_line. - Return i = data[1] - ''' - context, i, delta_c, delta_p, delta_s, bs_nl = data - # All ScanState classes must have a context ivar. - self.context = context - self.curlies += delta_c - ### Update {|{x=cap_name}|}_ScanState ivars - # self.bs_nl = bs_nl - # self.parens += delta_p - # self.squares += delta_s - return i - - - -True: same as recent_files_group, except that even files (basenames) which are unique -have their containing path listed in the submenu - so visual clutter is reduced -but you can still see where things come from before you load them. - -False: don't use submenus for multiple path entries, unless recent_files_group -is true (and recent_files_omit_directories is False) - - -True: show user tips on startup. - - - - -def check(codeString, filename, reporter=None): - """ - Check the Python source given by C{codeString} for flakes. - - @param codeString: The Python source to check. - @type codeString: C{str} - - @param filename: The name of the file the source came from, used to report - errors. - @type filename: C{str} - - @param reporter: A L{Reporter} instance, where errors and warnings will be - reported. - - @return: The number of warnings emitted. - @rtype: C{int} - """ - if reporter is None: - reporter = modReporter._makeDefaultReporter() - # First, compile into an AST and handle syntax errors. - try: - tree = ast.parse(codeString, filename=filename) - except SyntaxError as e: - reporter.syntaxError(filename, e.args[0], e.lineno, e.offset, e.text) - return 1 - except Exception: - reporter.unexpectedError(filename, 'problem decoding source') - return 1 - # Okay, it's syntactically valid. Now check it. - w = checker.Checker(tree, filename=filename) - w.messages.sort(key=lambda m: m.lineno) - for warning in w.messages: - reporter.flake(warning) - return len(w.messages) - - - -def __init__(self, tree, filename='(none)', builtins=None, - withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): - self._nodeHandlers = {} - self._deferred = collections.deque() - self.deadScopes = [] - self.messages = [] - self.filename = filename - if builtins: - self.builtIns = self.builtIns.union(builtins) - self.withDoctest = withDoctest - self.exceptHandlers = [()] - self.root = tree - - self.scopeStack = [] - try: - scope_tp = Checker._ast_node_scope[type(tree)] - except KeyError: - raise RuntimeError('No scope implemented for the node %r' % tree) - - with self.in_scope(scope_tp): - for builtin in self.builtIns: - self.addBinding(None, Builtin(builtin)) - self.handleChildren(tree) - self._run_deferred() - - self.checkDeadScopes() - - if file_tokens: - warnings.warn( - '`file_tokens` will be removed in a future version', - stacklevel=2, - ) - - -def deferFunction(self, callable): - """ - Schedule a function handler to be called just before completion. - - This is used for handling function bodies, which must be deferred - because code later in the file might modify the global scope. When - `callable` is called, the scope at the time this is called will be - restored, however it will contain any new bindings added to it. - """ - self._deferred.append((callable, self.scopeStack[:], self.offset)) - - -def _run_deferred(self): - orig = (self.scopeStack, self.offset) - - while self._deferred: - handler, scope, offset = self._deferred.popleft() - self.scopeStack, self.offset = scope, offset - handler() - - self.scopeStack, self.offset = orig - - -def _in_doctest(self): - return (len(self.scopeStack) >= 2 and - isinstance(self.scopeStack[1], DoctestScope)) - - -@property -def futuresAllowed(self): - if not all(isinstance(scope, ModuleScope) - for scope in self.scopeStack): - return False - - return self.scope._futures_allowed - -@futuresAllowed.setter -def futuresAllowed(self, value): - assert value is False - if isinstance(self.scope, ModuleScope): - self.scope._futures_allowed = False - - -@property -def annotationsFutureEnabled(self): - scope = self.scopeStack[0] - if not isinstance(scope, ModuleScope): - return False - return scope._annotations_future_enabled - -@annotationsFutureEnabled.setter -def annotationsFutureEnabled(self, value): - assert value is True - assert isinstance(self.scope, ModuleScope) - self.scope._annotations_future_enabled = True - - -@property -def scope(self): - return self.scopeStack[-1] - -@contextlib.contextmanager -def in_scope(self, cls): - self.scopeStack.append(cls()) - try: - yield - finally: - self.deadScopes.append(self.scopeStack.pop()) - - -def checkPath(filename, reporter=None): - """ - Check the given path, printing out any warnings detected. - - @param reporter: A L{Reporter} instance, where errors and warnings will be - reported. - - @return: the number of warnings printed - """ - if reporter is None: - reporter = modReporter._makeDefaultReporter() - try: - with open(filename, 'rb') as f: - codestr = f.read() - except OSError as e: - reporter.unexpectedError(filename, e.args[1]) - return 1 - return check(codestr, filename, reporter) - - - -def checkDeadScopes(self): - """ - Look at scopes which have been fully examined and report names in them - which were imported but unused. - """ - for scope in self.deadScopes: - # imports in classes are public members - if isinstance(scope, ClassScope): - continue - - if isinstance(scope, FunctionScope): - for name, binding in scope.unused_assignments(): - self.report(messages.UnusedVariable, binding.source, name) - for name, binding in scope.unused_annotations(): - self.report(messages.UnusedAnnotation, binding.source, name) - - all_binding = scope.get('__all__') - if all_binding and not isinstance(all_binding, ExportBinding): - all_binding = None - - if all_binding: - all_names = set(all_binding.names) - undefined = [ - name for name in all_binding.names - if name not in scope - ] - else: - all_names = undefined = [] - - if undefined: - if not scope.importStarred and \ - os.path.basename(self.filename) != '__init__.py': - # Look for possible mistakes in the export list - for name in undefined: - self.report(messages.UndefinedExport, - scope['__all__'].source, name) - - # mark all import '*' as used by the undefined in __all__ - if scope.importStarred: - from_list = [] - for binding in scope.values(): - if isinstance(binding, StarImportation): - binding.used = all_binding - from_list.append(binding.fullName) - # report * usage, with a list of possible sources - from_list = ', '.join(sorted(from_list)) - for name in undefined: - self.report(messages.ImportStarUsage, - scope['__all__'].source, name, from_list) - - # Look for imported names that aren't used. - for value in scope.values(): - if isinstance(value, Importation): - used = value.used or value.name in all_names - if not used: - messg = messages.UnusedImport - self.report(messg, value.source, str(value)) - for node in value.redefined: - if isinstance(self.getParent(node), FOR_TYPES): - messg = messages.ImportShadowedByLoopVar - elif used: - continue - else: - messg = messages.RedefinedWhileUnused - self.report(messg, node, value.name, value.source) - - -def report(self, messageClass, *args, **kwargs): - self.messages.append(messageClass(self.filename, *args, **kwargs)) - - -def getParent(self, node): - # Lookup the first parent which is not Tuple, List or Starred - while True: - node = node._pyflakes_parent - if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): - return node - - -def getCommonAncestor(self, lnode, rnode, stop): - if ( - stop in (lnode, rnode) or - not ( - hasattr(lnode, '_pyflakes_parent') and - hasattr(rnode, '_pyflakes_parent') - ) - ): - return None - if lnode is rnode: - return lnode - - if (lnode._pyflakes_depth > rnode._pyflakes_depth): - return self.getCommonAncestor(lnode._pyflakes_parent, rnode, stop) - if (lnode._pyflakes_depth < rnode._pyflakes_depth): - return self.getCommonAncestor(lnode, rnode._pyflakes_parent, stop) - return self.getCommonAncestor( - lnode._pyflakes_parent, - rnode._pyflakes_parent, - stop, - ) - - -def descendantOf(self, node, ancestors, stop): - for a in ancestors: - if self.getCommonAncestor(node, a, stop): - return True - return False - - -def _getAncestor(self, node, ancestor_type): - parent = node - while True: - if parent is self.root: - return None - parent = self.getParent(parent) - if isinstance(parent, ancestor_type): - return parent - - -def getScopeNode(self, node): - return self._getAncestor(node, tuple(Checker._ast_node_scope.keys())) - - -def differentForks(self, lnode, rnode): - """True, if lnode and rnode are located on different forks of IF/TRY""" - ancestor = self.getCommonAncestor(lnode, rnode, self.root) - parts = getAlternatives(ancestor) - if parts: - for items in parts: - if self.descendantOf(lnode, items, ancestor) ^ \ - self.descendantOf(rnode, items, ancestor): - return True - return False - - -def addBinding(self, node, value): - """ - Called when a binding is altered. - - - `node` is the statement responsible for the change - - `value` is the new value, a Binding instance - """ - # assert value.source in (node, node._pyflakes_parent): - for scope in self.scopeStack[::-1]: - if value.name in scope: - break - existing = scope.get(value.name) - - if (existing and not isinstance(existing, Builtin) and - not self.differentForks(node, existing.source)): - - parent_stmt = self.getParent(value.source) - if isinstance(existing, Importation) and isinstance(parent_stmt, FOR_TYPES): - self.report(messages.ImportShadowedByLoopVar, - node, value.name, existing.source) - - elif scope is self.scope: - if ( - (not existing.used and value.redefines(existing)) and - (value.name != '_' or isinstance(existing, Importation)) and - not is_typing_overload(existing, self.scopeStack) - ): - self.report(messages.RedefinedWhileUnused, - node, value.name, existing.source) - - elif isinstance(existing, Importation) and value.redefines(existing): - existing.redefined.append(node) - - if value.name in self.scope: - # then assume the rebound name is used as a global or within a loop - value.used = self.scope[value.name].used - - # don't treat annotations as assignments if there is an existing value - # in scope - if value.name not in self.scope or not isinstance(value, Annotation): - if isinstance(value, NamedExprAssignment): - # PEP 572: use scope in which outermost generator is defined - scope = next( - scope - for scope in reversed(self.scopeStack) - if not isinstance(scope, GeneratorScope) - ) - # it may be a re-assignment to an already existing name - scope.setdefault(value.name, value) - else: - self.scope[value.name] = value - - -def _unknown_handler(self, node): - # this environment variable configures whether to error on unknown - # ast types. - # - # this is silent by default but the error is enabled for the pyflakes - # testsuite. - # - # this allows new syntax to be added to python without *requiring* - # changes from the pyflakes side. but will still produce an error - # in the pyflakes testsuite (so more specific handling can be added if - # needed). - if os.environ.get('PYFLAKES_ERROR_UNKNOWN'): - raise NotImplementedError(f'Unexpected type: {type(node)}') - else: - self.handleChildren(node) - - -def isPythonFile(filename): - """Return True if filename points to a Python file.""" - if filename.endswith('.py'): - return True - - # Avoid obvious Emacs backup files - if filename.endswith("~"): - return False - - max_bytes = 128 - - try: - with open(filename, 'rb') as f: - text = f.read(max_bytes) - if not text: - return False - except OSError: - return False - - return PYTHON_SHEBANG_REGEX.match(text) - - - -def getNodeHandler(self, node_class): - try: - return self._nodeHandlers[node_class] - except KeyError: - nodeType = node_class.__name__.upper() - self._nodeHandlers[node_class] = handler = getattr( - self, nodeType, self._unknown_handler, - ) - return handler - - -def handleNodeLoad(self, node, parent): - name = getNodeName(node) - if not name: - return - - # only the following can access class scoped variables (since classes - # aren't really a scope) - # - direct accesses (not within a nested scope) - # - generators - # - type annotations (for generics, etc.) - can_access_class_vars = None - importStarred = None - - # try enclosing function scopes and global scope - for scope in self.scopeStack[-1::-1]: - if isinstance(scope, ClassScope): - if name == '__class__': - return - elif can_access_class_vars is False: - # only generators used in a class scope can access the - # names of the class. this is skipped during the first - # iteration - continue - - binding = scope.get(name, None) - if isinstance(binding, Annotation) and not self._in_postponed_annotation: - scope[name].used = (self.scope, node) - continue - - if name == 'print' and isinstance(binding, Builtin): - if (isinstance(parent, ast.BinOp) and - isinstance(parent.op, ast.RShift)): - self.report(messages.InvalidPrintSyntax, node) - - try: - scope[name].used = (self.scope, node) - - # if the name of SubImportation is same as - # alias of other Importation and the alias - # is used, SubImportation also should be marked as used. - n = scope[name] - if isinstance(n, Importation) and n._has_alias(): - try: - scope[n.fullName].used = (self.scope, node) - except KeyError: - pass - except KeyError: - pass - else: - return - - importStarred = importStarred or scope.importStarred - - if can_access_class_vars is not False: - can_access_class_vars = isinstance( - scope, (TypeScope, GeneratorScope), - ) - - if importStarred: - from_list = [] - - for scope in self.scopeStack[-1::-1]: - for binding in scope.values(): - if isinstance(binding, StarImportation): - # mark '*' imports as used for each scope - binding.used = (self.scope, node) - from_list.append(binding.fullName) - - # report * usage, with a list of possible sources - from_list = ', '.join(sorted(from_list)) - self.report(messages.ImportStarUsage, node, name, from_list) - return - - if name == '__path__' and os.path.basename(self.filename) == '__init__.py': - # the special name __path__ is valid only in packages - return - - if name in DetectClassScopedMagic.names and isinstance(self.scope, ClassScope): - return - - # protected with a NameError handler? - if 'NameError' not in self.exceptHandlers[-1]: - self.report(messages.UndefinedName, node, name) - - -def handleNodeStore(self, node): - name = getNodeName(node) - if not name: - return - # if the name hasn't already been defined in the current scope - if isinstance(self.scope, FunctionScope) and name not in self.scope: - # for each function or module scope above us - for scope in self.scopeStack[:-1]: - if not isinstance(scope, (FunctionScope, ModuleScope)): - continue - # if the name was defined in that scope, and the name has - # been accessed already in the current scope, and hasn't - # been declared global - used = name in scope and scope[name].used - if used and used[0] is self.scope and name not in self.scope.globals: - # then it's probably a mistake - self.report(messages.UndefinedLocal, - scope[name].used[1], name, scope[name].source) - break - - parent_stmt = self.getParent(node) - if isinstance(parent_stmt, ast.AnnAssign) and parent_stmt.value is None: - binding = Annotation(name, node) - elif isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( - parent_stmt != node._pyflakes_parent and - not self.isLiteralTupleUnpacking(parent_stmt)): - binding = Binding(name, node) - elif ( - name == '__all__' and - isinstance(self.scope, ModuleScope) and - isinstance( - node._pyflakes_parent, - (ast.Assign, ast.AugAssign, ast.AnnAssign) - ) - ): - binding = ExportBinding(name, node._pyflakes_parent, self.scope) - elif isinstance(parent_stmt, ast.NamedExpr): - binding = NamedExprAssignment(name, node) - else: - binding = Assignment(name, node) - self.addBinding(node, binding) - - -def handleNodeDelete(self, node): - - def on_conditional_branch(): - """ - Return `True` if node is part of a conditional body. - """ - current = getattr(node, '_pyflakes_parent', None) - while current: - if isinstance(current, (ast.If, ast.While, ast.IfExp)): - return True - current = getattr(current, '_pyflakes_parent', None) - return False - - name = getNodeName(node) - if not name: - return - - if on_conditional_branch(): - # We cannot predict if this conditional branch is going to - # be executed. - return - - if isinstance(self.scope, FunctionScope) and name in self.scope.globals: - self.scope.globals.remove(name) - else: - try: - del self.scope[name] - except KeyError: - self.report(messages.UndefinedName, node, name) - - -@contextlib.contextmanager -def _enter_annotation(self, ann_type=AnnotationState.BARE): - orig, self._in_annotation = self._in_annotation, ann_type - try: - yield - finally: - self._in_annotation = orig - - -@property -def _in_postponed_annotation(self): - return ( - self._in_annotation == AnnotationState.STRING or - self.annotationsFutureEnabled - ) - - -def handleChildren(self, tree, omit=None): - for node in iter_child_nodes(tree, omit=omit): - self.handleNode(node, tree) - - -def isLiteralTupleUnpacking(self, node): - if isinstance(node, ast.Assign): - for child in node.targets + [node.value]: - if not hasattr(child, 'elts'): - return False - return True - - -def isDocstring(self, node): - """ - Determine if the given node is a docstring, as long as it is at the - correct place in the node tree. - """ - return ( - isinstance(node, ast.Expr) and - isinstance(node.value, ast.Constant) and - isinstance(node.value.value, str) - ) - - -def getDocstring(self, node): - if ( - isinstance(node, ast.Expr) and - isinstance(node.value, ast.Constant) and - isinstance(node.value.value, str) - ): - return node.value.value, node.lineno - 1 - else: - return None, None - - -def iterSourceCode(paths): - """ - Iterate over all Python source files in C{paths}. - - @param paths: A list of paths. Directories will be recursed into and - any .py files found will be yielded. Any non-directories will be - yielded as-is. - """ - for path in paths: - if os.path.isdir(path): - for dirpath, dirnames, filenames in os.walk(path): - for filename in filenames: - full_path = os.path.join(dirpath, filename) - if isPythonFile(full_path): - yield full_path - else: - yield path - - - -def handleNode(self, node, parent): - if node is None: - return - if self.offset and getattr(node, 'lineno', None) is not None: - node.lineno += self.offset[0] - node.col_offset += self.offset[1] - if ( - self.futuresAllowed and - self.nodeDepth == 0 and - not isinstance(node, ast.ImportFrom) and - not self.isDocstring(node) - ): - self.futuresAllowed = False - self.nodeDepth += 1 - node._pyflakes_depth = self.nodeDepth - node._pyflakes_parent = parent - try: - handler = self.getNodeHandler(node.__class__) - handler(node) - finally: - self.nodeDepth -= 1 - - -_getDoctestExamples = doctest.DocTestParser().get_examples - -def handleDoctests(self, node): - try: - (docstring, node_lineno) = self.getDocstring(node.body[0]) - examples = docstring and self._getDoctestExamples(docstring) - except (ValueError, IndexError): - # e.g. line 6 of the docstring for <string> has inconsistent - # leading whitespace: ... - return - if not examples: - return - - # Place doctest in module scope - saved_stack = self.scopeStack - self.scopeStack = [self.scopeStack[0]] - node_offset = self.offset or (0, 0) - with self.in_scope(DoctestScope): - if '_' not in self.scopeStack[0]: - self.addBinding(None, Builtin('_')) - for example in examples: - try: - tree = ast.parse(example.source, "<doctest>") - except SyntaxError as e: - position = (node_lineno + example.lineno + e.lineno, - example.indent + 4 + (e.offset or 0)) - self.report(messages.DoctestSyntaxError, node, position) - else: - self.offset = (node_offset[0] + node_lineno + example.lineno, - node_offset[1] + example.indent + 4) - self.handleChildren(tree) - self.offset = node_offset - self.scopeStack = saved_stack - - -@in_string_annotation -def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err): - try: - tree = ast.parse(s) - except SyntaxError: - self.report(err, node, s) - return - - body = tree.body - if len(body) != 1 or not isinstance(body[0], ast.Expr): - self.report(err, node, s) - return - - parsed_annotation = tree.body[0].value - for descendant in ast.walk(parsed_annotation): - if ( - 'lineno' in descendant._attributes and - 'col_offset' in descendant._attributes - ): - descendant.lineno = ref_lineno - descendant.col_offset = ref_col_offset - - self.handleNode(parsed_annotation, node) - - -def handle_annotation_always_deferred(self, annotation, parent): - fn = in_annotation(Checker.handleNode) - self.deferFunction(lambda: fn(self, annotation, parent)) - - -@in_annotation -def handleAnnotation(self, annotation, node): - if ( - isinstance(annotation, ast.Constant) and - isinstance(annotation.value, str) - ): - # Defer handling forward annotation. - self.deferFunction(functools.partial( - self.handleStringAnnotation, - annotation.value, - node, - annotation.lineno, - annotation.col_offset, - messages.ForwardAnnotationSyntaxError, - )) - elif self.annotationsFutureEnabled: - self.handle_annotation_always_deferred(annotation, node) - else: - self.handleNode(annotation, node) - - -def ignore(self, node): - pass - - -# "stmt" type nodes -DELETE = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = ASYNCWITH = \ - EXPR = ASSIGN = handleChildren - -PASS = ignore - -# "expr" type nodes -BOOLOP = UNARYOP = SET = ATTRIBUTE = STARRED = NAMECONSTANT = \ - NAMEDEXPR = handleChildren - -def SUBSCRIPT(self, node): - if _is_name_or_attr(node.value, 'Literal'): - with self._enter_annotation(AnnotationState.NONE): - self.handleChildren(node) - elif _is_name_or_attr(node.value, 'Annotated'): - self.handleNode(node.value, node) - - # py39+ - if isinstance(node.slice, ast.Tuple): - slice_tuple = node.slice - # <py39 - elif ( - isinstance(node.slice, ast.Index) and - isinstance(node.slice.value, ast.Tuple) - ): - slice_tuple = node.slice.value - else: - slice_tuple = None - - # not a multi-arg `Annotated` - if slice_tuple is None or len(slice_tuple.elts) < 2: - self.handleNode(node.slice, node) - else: - # the first argument is the type - self.handleNode(slice_tuple.elts[0], node) - # the rest of the arguments are not - with self._enter_annotation(AnnotationState.NONE): - for arg in slice_tuple.elts[1:]: - self.handleNode(arg, node) - - self.handleNode(node.ctx, node) - else: - if _is_any_typing_member(node.value, self.scopeStack): - with self._enter_annotation(): - self.handleChildren(node) - else: - self.handleChildren(node) - - -def _handle_string_dot_format(self, node): - try: - placeholders = tuple(parse_format_string(node.func.value.value)) - except ValueError as e: - self.report(messages.StringDotFormatInvalidFormat, node, e) - return - - auto = None - next_auto = 0 - - placeholder_positional = set() - placeholder_named = set() - - def _add_key(fmtkey): - """Returns True if there is an error which should early-exit""" - nonlocal auto, next_auto - - if fmtkey is None: # end of string or `{` / `}` escapes - return False - - # attributes / indices are allowed in `.format(...)` - fmtkey, _, _ = fmtkey.partition('.') - fmtkey, _, _ = fmtkey.partition('[') - - try: - fmtkey = int(fmtkey) - except ValueError: - pass - else: # fmtkey was an integer - if auto is True: - self.report(messages.StringDotFormatMixingAutomatic, node) - return True - else: - auto = False - - if fmtkey == '': - if auto is False: - self.report(messages.StringDotFormatMixingAutomatic, node) - return True - else: - auto = True - - fmtkey = next_auto - next_auto += 1 - - if isinstance(fmtkey, int): - placeholder_positional.add(fmtkey) - else: - placeholder_named.add(fmtkey) - - return False - - for _, fmtkey, spec, _ in placeholders: - if _add_key(fmtkey): - return - - # spec can also contain format specifiers - if spec is not None: - try: - spec_placeholders = tuple(parse_format_string(spec)) - except ValueError as e: - self.report(messages.StringDotFormatInvalidFormat, node, e) - return - - for _, spec_fmtkey, spec_spec, _ in spec_placeholders: - # can't recurse again - if spec_spec is not None and '{' in spec_spec: - self.report( - messages.StringDotFormatInvalidFormat, - node, - 'Max string recursion exceeded', - ) - return - if _add_key(spec_fmtkey): - return - - # bail early if there is *args or **kwargs - if ( - # *args - any(isinstance(arg, ast.Starred) for arg in node.args) or - # **kwargs - any(kwd.arg is None for kwd in node.keywords) - ): - return - - substitution_positional = set(range(len(node.args))) - substitution_named = {kwd.arg for kwd in node.keywords} - - extra_positional = substitution_positional - placeholder_positional - extra_named = substitution_named - placeholder_named - - missing_arguments = ( - (placeholder_positional | placeholder_named) - - (substitution_positional | substitution_named) - ) - - if extra_positional: - self.report( - messages.StringDotFormatExtraPositionalArguments, - node, - ', '.join(sorted(str(x) for x in extra_positional)), - ) - if extra_named: - self.report( - messages.StringDotFormatExtraNamedArguments, - node, - ', '.join(sorted(extra_named)), - ) - if missing_arguments: - self.report( - messages.StringDotFormatMissingArgument, - node, - ', '.join(sorted(str(x) for x in missing_arguments)), - ) - - -def CALL(self, node): - if ( - isinstance(node.func, ast.Attribute) and - isinstance(node.func.value, ast.Constant) and - isinstance(node.func.value.value, str) and - node.func.attr == 'format' - ): - self._handle_string_dot_format(node) - - omit = [] - annotated = [] - not_annotated = [] - - if ( - _is_typing(node.func, 'cast', self.scopeStack) and - len(node.args) >= 1 - ): - with self._enter_annotation(): - self.handleNode(node.args[0], node) - - elif _is_typing(node.func, 'TypeVar', self.scopeStack): - - # TypeVar("T", "int", "str") - omit += ["args"] - annotated += [arg for arg in node.args[1:]] - - # TypeVar("T", bound="str") - omit += ["keywords"] - annotated += [k.value for k in node.keywords if k.arg == "bound"] - not_annotated += [ - (k, ["value"] if k.arg == "bound" else None) - for k in node.keywords - ] - - elif _is_typing(node.func, "TypedDict", self.scopeStack): - # TypedDict("a", {"a": int}) - if len(node.args) > 1 and isinstance(node.args[1], ast.Dict): - omit += ["args"] - annotated += node.args[1].values - not_annotated += [ - (arg, ["values"] if i == 1 else None) - for i, arg in enumerate(node.args) - ] - - # TypedDict("a", a=int) - omit += ["keywords"] - annotated += [k.value for k in node.keywords] - not_annotated += [(k, ["value"]) for k in node.keywords] - - elif _is_typing(node.func, "NamedTuple", self.scopeStack): - # NamedTuple("a", [("a", int)]) - if ( - len(node.args) > 1 and - isinstance(node.args[1], (ast.Tuple, ast.List)) and - all(isinstance(x, (ast.Tuple, ast.List)) and - len(x.elts) == 2 for x in node.args[1].elts) - ): - omit += ["args"] - annotated += [elt.elts[1] for elt in node.args[1].elts] - not_annotated += [(elt.elts[0], None) for elt in node.args[1].elts] - not_annotated += [ - (arg, ["elts"] if i == 1 else None) - for i, arg in enumerate(node.args) - ] - not_annotated += [(elt, "elts") for elt in node.args[1].elts] - - # NamedTuple("a", a=int) - omit += ["keywords"] - annotated += [k.value for k in node.keywords] - not_annotated += [(k, ["value"]) for k in node.keywords] - - if omit: - with self._enter_annotation(AnnotationState.NONE): - for na_node, na_omit in not_annotated: - self.handleChildren(na_node, omit=na_omit) - self.handleChildren(node, omit=omit) - - with self._enter_annotation(): - for annotated_node in annotated: - self.handleNode(annotated_node, node) - else: - self.handleChildren(node) - - -def _handle_percent_format(self, node): - try: - placeholders = parse_percent_format(node.left.value) - except ValueError: - self.report( - messages.PercentFormatInvalidFormat, - node, - 'incomplete format', - ) - return - - named = set() - positional_count = 0 - positional = None - for _, placeholder in placeholders: - if placeholder is None: - continue - name, _, width, precision, conversion = placeholder - - if conversion == '%': - continue - - if conversion not in VALID_CONVERSIONS: - self.report( - messages.PercentFormatUnsupportedFormatCharacter, - node, - conversion, - ) - - if positional is None and conversion: - positional = name is None - - for part in (width, precision): - if part is not None and '*' in part: - if not positional: - self.report( - messages.PercentFormatStarRequiresSequence, - node, - ) - else: - positional_count += 1 - - if positional and name is not None: - self.report( - messages.PercentFormatMixedPositionalAndNamed, - node, - ) - return - elif not positional and name is None: - self.report( - messages.PercentFormatMixedPositionalAndNamed, - node, - ) - return - - if positional: - positional_count += 1 - else: - named.add(name) - - if ( - isinstance(node.right, (ast.List, ast.Tuple)) and - # does not have any *splats (py35+ feature) - not any( - isinstance(elt, ast.Starred) - for elt in node.right.elts - ) - ): - substitution_count = len(node.right.elts) - if positional and positional_count != substitution_count: - self.report( - messages.PercentFormatPositionalCountMismatch, - node, - positional_count, - substitution_count, - ) - elif not positional: - self.report(messages.PercentFormatExpectedMapping, node) - - if ( - isinstance(node.right, ast.Dict) and - all( - isinstance(k, ast.Constant) and isinstance(k.value, str) - for k in node.right.keys - ) - ): - if positional and positional_count > 1: - self.report(messages.PercentFormatExpectedSequence, node) - return - - substitution_keys = {k.value for k in node.right.keys} - extra_keys = substitution_keys - named - missing_keys = named - substitution_keys - if not positional and extra_keys: - self.report( - messages.PercentFormatExtraNamedArguments, - node, - ', '.join(sorted(extra_keys)), - ) - if not positional and missing_keys: - self.report( - messages.PercentFormatMissingArgument, - node, - ', '.join(sorted(missing_keys)), - ) - - -def checkRecursive(paths, reporter): - """ - Recursively check all source files in C{paths}. - - @param paths: A list of paths to Python source files and directories - containing Python source files. - @param reporter: A L{Reporter} where all of the warnings and errors - will be reported to. - @return: The number of warnings found. - """ - warnings = 0 - for sourcePath in iterSourceCode(paths): - warnings += checkPath(sourcePath, reporter) - return warnings - - - -def BINOP(self, node): - if ( - isinstance(node.op, ast.Mod) and - isinstance(node.left, ast.Constant) and - isinstance(node.left.value, str) - ): - self._handle_percent_format(node) - self.handleChildren(node) - - -def CONSTANT(self, node): - if isinstance(node.value, str) and self._in_annotation: - fn = functools.partial( - self.handleStringAnnotation, - node.value, - node, - node.lineno, - node.col_offset, - messages.ForwardAnnotationSyntaxError, - ) - self.deferFunction(fn) - -# "slice" type nodes -SLICE = EXTSLICE = INDEX = handleChildren - -# expression contexts are node instances too, though being constants -LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore - -# same for operators -AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ - BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ - EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = \ - MATMULT = ignore - - -def RAISE(self, node): - self.handleChildren(node) - - arg = node.exc - - if isinstance(arg, ast.Call): - if is_notimplemented_name_node(arg.func): - # Handle "raise NotImplemented(...)" - self.report(messages.RaiseNotImplemented, node) - elif is_notimplemented_name_node(arg): - # Handle "raise NotImplemented" - self.report(messages.RaiseNotImplemented, node) - - -_in_fstring = False - -def JOINEDSTR(self, node): - if ( - # the conversion / etc. flags are parsed as f-strings without - # placeholders - not self._in_fstring and - not any(isinstance(x, ast.FormattedValue) for x in node.values) - ): - self.report(messages.FStringMissingPlaceholders, node) - - self._in_fstring, orig = True, self._in_fstring - try: - self.handleChildren(node) - finally: - self._in_fstring = orig - - -def DICT(self, node): - # Complain if there are duplicate keys with different values - # If they have the same value it's not going to cause potentially - # unexpected behaviour so we'll not complain. - keys = [ - convert_to_value(key) for key in node.keys - ] - - key_counts = collections.Counter(keys) - duplicate_keys = [ - key for key, count in key_counts.items() - if count > 1 - ] - - for key in duplicate_keys: - key_indices = [i for i, i_key in enumerate(keys) if i_key == key] - - values = collections.Counter( - convert_to_value(node.values[index]) - for index in key_indices - ) - if any(count == 1 for value, count in values.items()): - for key_index in key_indices: - key_node = node.keys[key_index] - if isinstance(key, VariableKey): - self.report(messages.MultiValueRepeatedKeyVariable, - key_node, - key.name) - else: - self.report( - messages.MultiValueRepeatedKeyLiteral, - key_node, - key, - ) - self.handleChildren(node) - - -def IF(self, node): - if isinstance(node.test, ast.Tuple) and node.test.elts != []: - self.report(messages.IfTuple, node) - self.handleChildren(node) - -IFEXP = IF - - -def ASSERT(self, node): - if isinstance(node.test, ast.Tuple) and node.test.elts != []: - self.report(messages.AssertTuple, node) - self.handleChildren(node) - - -def GLOBAL(self, node): - """ - Keep track of globals declarations. - """ - global_scope_index = 1 if self._in_doctest() else 0 - global_scope = self.scopeStack[global_scope_index] - - # Ignore 'global' statement in global scope. - if self.scope is not global_scope: - - # One 'global' statement can bind multiple (comma-delimited) names. - for node_name in node.names: - node_value = Assignment(node_name, node) - - # Remove UndefinedName messages already reported for this name. - # TODO: if the global is not used in this scope, it does not - # become a globally defined name. See test_unused_global. - self.messages = [ - m for m in self.messages if not - isinstance(m, messages.UndefinedName) or - m.message_args[0] != node_name] - - # Bind name to global scope if it doesn't exist already. - global_scope.setdefault(node_name, node_value) - - # Bind name to non-global scopes, but as already "used". - node_value.used = (global_scope, node) - for scope in self.scopeStack[global_scope_index + 1:]: - scope[node_name] = node_value - -NONLOCAL = GLOBAL - - -def GENERATOREXP(self, node): - with self.in_scope(GeneratorScope): - self.handleChildren(node) - -LISTCOMP = DICTCOMP = SETCOMP = GENERATOREXP - - -def NAME(self, node): - """ - Handle occurrence of Name (which can be a load/store/delete access.) - """ - # Locate the name in locals / function / globals scopes. - if isinstance(node.ctx, ast.Load): - self.handleNodeLoad(node, self.getParent(node)) - if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and - isinstance(node._pyflakes_parent, ast.Call)): - # we are doing locals() call in current scope - self.scope.usesLocals = True - elif isinstance(node.ctx, ast.Store): - self.handleNodeStore(node) - elif isinstance(node.ctx, ast.Del): - self.handleNodeDelete(node) - else: - # Unknown context - raise RuntimeError(f"Got impossible expression context: {node.ctx!r}") - - -def _exitOnSignal(sigName, message): - """Handles a signal with sys.exit. - - Some of these signals (SIGPIPE, for example) don't exist or are invalid on - Windows. So, ignore errors that might arise. - """ - import signal - - try: - sigNumber = getattr(signal, sigName) - except AttributeError: - # the signal constants defined in the signal module are defined by - # whether the C library supports them or not. So, SIGPIPE might not - # even be defined. - return - - def handler(sig, f): - sys.exit(message) - - try: - signal.signal(sigNumber, handler) - except ValueError: - # It's also possible the signal is defined, but then it's invalid. In - # this case, signal.signal raises ValueError. - pass - - - -def CONTINUE(self, node): - # Walk the tree up until we see a loop (OK), a function or class - # definition (not OK), for 'continue', a finally block (not OK), or - # the top module scope (not OK) - n = node - while hasattr(n, '_pyflakes_parent'): - n, n_child = n._pyflakes_parent, n - if isinstance(n, (ast.While, ast.For, ast.AsyncFor)): - # Doesn't apply unless it's in the loop itself - if n_child not in n.orelse: - return - if isinstance(n, (ast.FunctionDef, ast.ClassDef)): - break - if isinstance(node, ast.Continue): - self.report(messages.ContinueOutsideLoop, node) - else: # ast.Break - self.report(messages.BreakOutsideLoop, node) - -BREAK = CONTINUE - - -def RETURN(self, node): - if isinstance(self.scope, (ClassScope, ModuleScope)): - self.report(messages.ReturnOutsideFunction, node) - return - - if ( - node.value and - hasattr(self.scope, 'returnValue') and - not self.scope.returnValue - ): - self.scope.returnValue = node.value - self.handleNode(node.value, node) - - -def YIELD(self, node): - if isinstance(self.scope, (ClassScope, ModuleScope)): - self.report(messages.YieldOutsideFunction, node) - return - - self.handleNode(node.value, node) - -AWAIT = YIELDFROM = YIELD - - -def FUNCTIONDEF(self, node): - for deco in node.decorator_list: - self.handleNode(deco, node) - - with self._type_param_scope(node): - self.LAMBDA(node) - - self.addBinding(node, FunctionDefinition(node.name, node)) - # doctest does not process doctest within a doctest, - # or in nested functions. - if (self.withDoctest and - not self._in_doctest() and - not isinstance(self.scope, FunctionScope)): - self.deferFunction(lambda: self.handleDoctests(node)) - - -ASYNCFUNCTIONDEF = FUNCTIONDEF - -def LAMBDA(self, node): - args = [] - annotations = [] - - for arg in node.args.posonlyargs: - args.append(arg.arg) - annotations.append(arg.annotation) - for arg in node.args.args + node.args.kwonlyargs: - args.append(arg.arg) - annotations.append(arg.annotation) - defaults = node.args.defaults + node.args.kw_defaults - - has_annotations = not isinstance(node, ast.Lambda) - - for arg_name in ('vararg', 'kwarg'): - wildcard = getattr(node.args, arg_name) - if not wildcard: - continue - args.append(wildcard.arg) - if has_annotations: - annotations.append(wildcard.annotation) - - if has_annotations: - annotations.append(node.returns) - - if len(set(args)) < len(args): - for (idx, arg) in enumerate(args): - if arg in args[:idx]: - self.report(messages.DuplicateArgument, node, arg) - - for annotation in annotations: - self.handleAnnotation(annotation, node) - - for default in defaults: - self.handleNode(default, node) - - def runFunction(): - with self.in_scope(FunctionScope): - self.handleChildren( - node, - omit=('decorator_list', 'returns', 'type_params'), - ) - - self.deferFunction(runFunction) - - -def ARGUMENTS(self, node): - self.handleChildren(node, omit=('defaults', 'kw_defaults')) - - -def ARG(self, node): - self.addBinding(node, Argument(node.arg, self.getScopeNode(node))) - - -def CLASSDEF(self, node): - """ - Check names used in a class definition, including its decorators, base - classes, and the body of its definition. Additionally, add its name to - the current scope. - """ - for deco in node.decorator_list: - self.handleNode(deco, node) - - with self._type_param_scope(node): - for baseNode in node.bases: - self.handleNode(baseNode, node) - for keywordNode in node.keywords: - self.handleNode(keywordNode, node) - with self.in_scope(ClassScope): - # doctest does not process doctest within a doctest - # classes within classes are processed. - if (self.withDoctest and - not self._in_doctest() and - not isinstance(self.scope, FunctionScope)): - self.deferFunction(lambda: self.handleDoctests(node)) - for stmt in node.body: - self.handleNode(stmt, node) - - self.addBinding(node, ClassDefinition(node.name, node)) - - -def AUGASSIGN(self, node): - self.handleNodeLoad(node.target, node) - self.handleNode(node.value, node) - self.handleNode(node.target, node) - - -def TUPLE(self, node): - if isinstance(node.ctx, ast.Store): - # Python 3 advanced tuple unpacking: a, *b, c = d. - # Only one starred expression is allowed, and no more than 1<<8 - # assignments are allowed before a stared expression. There is - # also a limit of 1<<24 expressions after the starred expression, - # which is impossible to test due to memory restrictions, but we - # add it here anyway - has_starred = False - star_loc = -1 - for i, n in enumerate(node.elts): - if isinstance(n, ast.Starred): - if has_starred: - self.report(messages.TwoStarredExpressions, node) - # The SyntaxError doesn't distinguish two from more - # than two. - break - has_starred = True - star_loc = i - if star_loc >= 1 << 8 or len(node.elts) - star_loc - 1 >= 1 << 24: - self.report(messages.TooManyExpressionsInStarredAssignment, node) - self.handleChildren(node) - -LIST = TUPLE - - -def _get_version(): - """ - Retrieve and format package version along with python version & OS used - """ - return ('%s Python %s on %s' % - (__version__, platform.python_version(), platform.system())) - - - -def IMPORT(self, node): - for alias in node.names: - if '.' in alias.name and not alias.asname: - importation = SubmoduleImportation(alias.name, node) - else: - name = alias.asname or alias.name - importation = Importation(name, node, alias.name) - self.addBinding(node, importation) - - -def IMPORTFROM(self, node): - if node.module == '__future__': - if not self.futuresAllowed: - self.report(messages.LateFutureImport, node) - else: - self.futuresAllowed = False - - module = ('.' * node.level) + (node.module or '') - - for alias in node.names: - name = alias.asname or alias.name - if node.module == '__future__': - importation = FutureImportation(name, node, self.scope) - if alias.name not in __future__.all_feature_names: - self.report(messages.FutureFeatureNotDefined, - node, alias.name) - if alias.name == 'annotations': - self.annotationsFutureEnabled = True - elif alias.name == '*': - if not isinstance(self.scope, ModuleScope): - self.report(messages.ImportStarNotPermitted, - node, module) - continue - - self.scope.importStarred = True - self.report(messages.ImportStarUsed, node, module) - importation = StarImportation(module, node) - else: - importation = ImportationFrom(name, node, - module, alias.name) - self.addBinding(node, importation) - - -def TRY(self, node): - handler_names = [] - # List the exception handlers - for i, handler in enumerate(node.handlers): - if isinstance(handler.type, ast.Tuple): - for exc_type in handler.type.elts: - handler_names.append(getNodeName(exc_type)) - elif handler.type: - handler_names.append(getNodeName(handler.type)) - - if handler.type is None and i < len(node.handlers) - 1: - self.report(messages.DefaultExceptNotLast, handler) - # Memorize the except handlers and process the body - self.exceptHandlers.append(handler_names) - for child in node.body: - self.handleNode(child, node) - self.exceptHandlers.pop() - # Process the other nodes: "except:", "else:", "finally:" - self.handleChildren(node, omit='body') - -TRYSTAR = TRY - - -def EXCEPTHANDLER(self, node): - if node.name is None: - self.handleChildren(node) - return - - # If the name already exists in the scope, modify state of existing - # binding. - if node.name in self.scope: - self.handleNodeStore(node) - - # 3.x: the name of the exception, which is not a Name node, but a - # simple string, creates a local that is only bound within the scope of - # the except: block. As such, temporarily remove the existing binding - # to more accurately determine if the name is used in the except: - # block. - - try: - prev_definition = self.scope.pop(node.name) - except KeyError: - prev_definition = None - - self.handleNodeStore(node) - self.handleChildren(node) - - # See discussion on https://github.com/PyCQA/pyflakes/pull/59 - - # We're removing the local name since it's being unbound after leaving - # the except: block and it's always unbound if the except: block is - # never entered. This will cause an "undefined name" error raised if - # the checked code tries to use the name afterwards. - # - # Unless it's been removed already. Then do nothing. - - try: - binding = self.scope.pop(node.name) - except KeyError: - pass - else: - if not binding.used: - self.report(messages.UnusedVariable, node, node.name) - - # Restore. - if prev_definition: - self.scope[node.name] = prev_definition - - -def ANNASSIGN(self, node): - self.handleAnnotation(node.annotation, node) - # If the assignment has value, handle the *value* now. - if node.value: - # If the annotation is `TypeAlias`, handle the *value* as an annotation. - if _is_typing(node.annotation, 'TypeAlias', self.scopeStack): - self.handleAnnotation(node.value, node) - else: - self.handleNode(node.value, node) - self.handleNode(node.target, node) - - -def COMPARE(self, node): - left = node.left - for op, right in zip(node.ops, node.comparators): - if ( - isinstance(op, (ast.Is, ast.IsNot)) and ( - _is_const_non_singleton(left) or - _is_const_non_singleton(right) - ) - ): - self.report(messages.IsLiteral, node) - left = right - - self.handleChildren(node) - - -MATCH = MATCH_CASE = MATCHCLASS = MATCHOR = MATCHSEQUENCE = handleChildren -MATCHSINGLETON = MATCHVALUE = handleChildren - -def _match_target(self, node): - self.handleNodeStore(node) - self.handleChildren(node) - -MATCHAS = MATCHMAPPING = MATCHSTAR = _match_target - - -@contextlib.contextmanager -def _type_param_scope(self, node): - with contextlib.ExitStack() as ctx: - if sys.version_info >= (3, 12): - ctx.enter_context(self.in_scope(TypeScope)) - for param in node.type_params: - self.handleNode(param, node) - yield - - -def TYPEVAR(self, node): - self.handleNodeStore(node) - self.handle_annotation_always_deferred(node.bound, node) - -PARAMSPEC = TYPEVARTUPLE = handleNodeStore - - -def TYPEALIAS(self, node): - self.handleNode(node.name, node) - with self._type_param_scope(node): - self.handle_annotation_always_deferred(node.value, node) - -def main(prog=None, args=None): - """Entry point for the script "pyflakes".""" - import argparse - - # Handle "Keyboard Interrupt" and "Broken pipe" gracefully - _exitOnSignal('SIGINT', '... stopped') - _exitOnSignal('SIGPIPE', 1) - - parser = argparse.ArgumentParser(prog=prog, - description='Check Python source files for errors') - parser.add_argument('-V', '--version', action='version', version=_get_version()) - parser.add_argument('path', nargs='*', - help='Path(s) of Python file(s) to check. STDIN if not given.') - args = parser.parse_args(args=args).path - reporter = modReporter._makeDefaultReporter() - if args: - warnings = checkRecursive(args, reporter) - else: - warnings = check(sys.stdin.read(), '<stdin>', reporter) - raise SystemExit(warnings > 0) - -@path pyflakes -""" -Provide the class Message and its subclasses. -""" - - -@others -@language python -@tabwidth -4 - -class Message: - message = '' - message_args = () - - def __init__(self, filename, loc): - self.filename = filename - self.lineno = loc.lineno - self.col = loc.col_offset - - def __str__(self): - return '{}:{}:{}: {}'.format(self.filename, self.lineno, self.col+1, - self.message % self.message_args) - - - -class UnusedImport(Message): - message = '%r imported but unused' - - def __init__(self, filename, loc, name): - Message.__init__(self, filename, loc) - self.message_args = (name,) - - - -class RedefinedWhileUnused(Message): - message = 'redefinition of unused %r from line %r' - - def __init__(self, filename, loc, name, orig_loc): - Message.__init__(self, filename, loc) - self.message_args = (name, orig_loc.lineno) - - - -class ImportShadowedByLoopVar(Message): - @others - -class ImportStarNotPermitted(Message): - @others - -class ImportStarUsed(Message): - @others - -class ImportStarUsage(Message): - @others - -class UndefinedName(Message): - @others - -class DoctestSyntaxError(Message): - @others - -@path pyflakes -""" -Main module. - -Implement the central Checker class. -Also, it models the Bindings and Scopes. -""" -import __future__ -import builtins -import ast -import collections -import contextlib -import doctest -import functools -import os -import re -import string -import sys -import warnings - -from pyflakes import messages - -<< checker.py: globals >> - - -@others -@language python -@tabwidth -4 - -class UndefinedExport(Message): - @others - -class UndefinedLocal(Message): - @others - -class DuplicateArgument(Message): - @others - -class MultiValueRepeatedKeyLiteral(Message): - @others - -class MultiValueRepeatedKeyVariable(Message): - @others - -class LateFutureImport(Message): - message = 'from __future__ imports must occur at the beginning of the file' - - - -class FutureFeatureNotDefined(Message): - """An undefined __future__ feature name was imported.""" - @others - -class UnusedVariable(Message): - """ - Indicates that a variable has been explicitly assigned to but not actually - used. - """ - @others - -class UnusedAnnotation(Message): - """ - Indicates that a variable has been explicitly annotated to but not actually - used. - """ - @others - -class ReturnOutsideFunction(Message): - """ - Indicates a return statement outside of a function/method. - """ - message = '\'return\' outside function' - - - -def getAlternatives(n): - if isinstance(n, ast.If): - return [n.body] - elif isinstance(n, ast.Try): - return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] - elif sys.version_info >= (3, 10) and isinstance(n, ast.Match): - return [mc.body for mc in n.cases] - - - -class YieldOutsideFunction(Message): - """ - Indicates a yield or yield from statement outside of a function/method. - """ - message = '\'yield\' outside function' - - - -# For whatever reason, Python gives different error messages for these two. We -# match the Python error message exactly. -class ContinueOutsideLoop(Message): - """ - Indicates a continue statement outside of a while or for loop. - """ - message = '\'continue\' not properly in loop' - - - -class BreakOutsideLoop(Message): - """ - Indicates a break statement outside of a while or for loop. - """ - message = '\'break\' outside loop' - - - -class DefaultExceptNotLast(Message): - """ - Indicates an except: block as not the last exception handler. - """ - message = 'default \'except:\' must be last' - - - -class TwoStarredExpressions(Message): - """ - Two or more starred expressions in an assignment (a, *b, *c = d). - """ - message = 'two starred expressions in assignment' - - - -class TooManyExpressionsInStarredAssignment(Message): - """ - Too many expressions in an assignment with star-unpacking - """ - message = 'too many expressions in star-unpacking assignment' - - - -class IfTuple(Message): - """ - Conditional test is a non-empty tuple literal, which are always True. - """ - message = '\'if tuple literal\' is always true, perhaps remove accidental comma?' - - - -class AssertTuple(Message): - """ - Assertion test is a non-empty tuple literal, which are always True. - """ - message = 'assertion is always true, perhaps remove parentheses?' - - - -class ForwardAnnotationSyntaxError(Message): - @others - -class RaiseNotImplemented(Message): - message = "'raise NotImplemented' should be 'raise NotImplementedError'" - - - -def _is_singleton(node): # type: (ast.AST) -> bool - return ( - isinstance(node, ast.Constant) and - isinstance(node.value, (bool, type(Ellipsis), type(None))) - ) - - - -class InvalidPrintSyntax(Message): - message = 'use of >> is invalid with print function' - - - -class IsLiteral(Message): - message = 'use ==/!= to compare constant literals (str, bytes, int, float, tuple)' - - - -class FStringMissingPlaceholders(Message): - message = 'f-string is missing placeholders' - - - -class StringDotFormatExtraPositionalArguments(Message): - @others - -class StringDotFormatExtraNamedArguments(Message): - @others - -class StringDotFormatMissingArgument(Message): - @others - -class StringDotFormatMixingAutomatic(Message): - message = "'...'.format(...) mixes automatic and manual numbering" - - - -class StringDotFormatInvalidFormat(Message): - @others - -class PercentFormatInvalidFormat(Message): - @others - -class PercentFormatMixedPositionalAndNamed(Message): - message = "'...' %% ... has mixed positional and named placeholders" - - - -def _is_tuple_constant(node): # type: (ast.AST) -> bool - return ( - isinstance(node, ast.Tuple) and - all(_is_constant(elt) for elt in node.elts) - ) - - - -class PercentFormatUnsupportedFormatCharacter(Message): - @others - -class PercentFormatPositionalCountMismatch(Message): - @others - -class PercentFormatExtraNamedArguments(Message): - @others - -class PercentFormatMissingArgument(Message): - @others - -class PercentFormatExpectedMapping(Message): - message = "'...' %% ... expected mapping but got sequence" - - - -class PercentFormatExpectedSequence(Message): - message = "'...' %% ... expected sequence but got mapping" - - - -class PercentFormatStarRequiresSequence(Message): - message = "'...' %% ... `*` specifier requires sequence" - -def _is_constant(node): - return isinstance(node, ast.Constant) or _is_tuple_constant(node) - - - -message = 'import %r from line %r shadowed by loop variable' - -def __init__(self, filename, loc, name, orig_loc): - Message.__init__(self, filename, loc) - self.message_args = (name, orig_loc.lineno) - - - -message = "'from %s import *' only allowed at module level" - -def __init__(self, filename, loc, modname): - Message.__init__(self, filename, loc) - self.message_args = (modname,) - - - -message = "'from %s import *' used; unable to detect undefined names" - -def __init__(self, filename, loc, modname): - Message.__init__(self, filename, loc) - self.message_args = (modname,) - - - -message = "%r may be undefined, or defined from star imports: %s" - -def __init__(self, filename, loc, name, from_list): - Message.__init__(self, filename, loc) - self.message_args = (name, from_list) - - - -message = 'undefined name %r' - -def __init__(self, filename, loc, name): - Message.__init__(self, filename, loc) - self.message_args = (name,) - - - -message = 'syntax error in doctest' - -def __init__(self, filename, loc, position=None): - Message.__init__(self, filename, loc) - if position: - (self.lineno, self.col) = position - self.message_args = () - - - -message = 'undefined name %r in __all__' - -def __init__(self, filename, loc, name): - Message.__init__(self, filename, loc) - self.message_args = (name,) - - - -message = 'local variable %r {0} referenced before assignment' - -default = 'defined in enclosing scope on line %r' -builtin = 'defined as a builtin' - -def __init__(self, filename, loc, name, orig_loc): - Message.__init__(self, filename, loc) - if orig_loc is None: - self.message = self.message.format(self.builtin) - self.message_args = name - else: - self.message = self.message.format(self.default) - self.message_args = (name, orig_loc.lineno) - - - -message = 'duplicate argument %r in function definition' - -def __init__(self, filename, loc, name): - Message.__init__(self, filename, loc) - self.message_args = (name,) - - - -def _is_const_non_singleton(node): # type: (ast.AST) -> bool - return _is_constant(node) and not _is_singleton(node) - - - -message = 'dictionary key %r repeated with different values' - -def __init__(self, filename, loc, key): - Message.__init__(self, filename, loc) - self.message_args = (key,) - - - -message = 'dictionary key variable %s repeated with different values' - -def __init__(self, filename, loc, key): - Message.__init__(self, filename, loc) - self.message_args = (key,) - - - -message = 'future feature %s is not defined' - -def __init__(self, filename, loc, name): - Message.__init__(self, filename, loc) - self.message_args = (name,) - - - -message = 'local variable %r is assigned to but never used' - -def __init__(self, filename, loc, names): - Message.__init__(self, filename, loc) - self.message_args = (names,) - - - -message = 'local variable %r is annotated but never used' - -def __init__(self, filename, loc, names): - Message.__init__(self, filename, loc) - self.message_args = (names,) - - - -message = 'syntax error in forward annotation %r' - -def __init__(self, filename, loc, annotation): - Message.__init__(self, filename, loc) - self.message_args = (annotation,) - - - -message = "'...'.format(...) has unused arguments at position(s): %s" - -def __init__(self, filename, loc, extra_positions): - Message.__init__(self, filename, loc) - self.message_args = (extra_positions,) - - - -message = "'...'.format(...) has unused named argument(s): %s" - -def __init__(self, filename, loc, extra_keywords): - Message.__init__(self, filename, loc) - self.message_args = (extra_keywords,) - - - -message = "'...'.format(...) is missing argument(s) for placeholder(s): %s" - -def __init__(self, filename, loc, missing_arguments): - Message.__init__(self, filename, loc) - self.message_args = (missing_arguments,) - - - -message = "'...'.format(...) has invalid format string: %s" - -def __init__(self, filename, loc, error): - Message.__init__(self, filename, loc) - self.message_args = (error,) - - - -def _is_name_or_attr(node, name): # type: (ast.AST, str) -> bool - return ( - (isinstance(node, ast.Name) and node.id == name) or - (isinstance(node, ast.Attribute) and node.attr == name) - ) - - - -message = "'...' %% ... has invalid format string: %s" - -def __init__(self, filename, loc, error): - Message.__init__(self, filename, loc) - self.message_args = (error,) - - - -message = "'...' %% ... has unsupported format character %r" - -def __init__(self, filename, loc, c): - Message.__init__(self, filename, loc) - self.message_args = (c,) - - - -message = "'...' %% ... has %d placeholder(s) but %d substitution(s)" - -def __init__(self, filename, loc, n_placeholders, n_substitutions): - Message.__init__(self, filename, loc) - self.message_args = (n_placeholders, n_substitutions) - - - -message = "'...' %% ... has unused named argument(s): %s" - -def __init__(self, filename, loc, extra_keywords): - Message.__init__(self, filename, loc) - self.message_args = (extra_keywords,) - - - -message = "'...' %% ... is missing argument(s) for placeholder(s): %s" - -def __init__(self, filename, loc, missing_arguments): - Message.__init__(self, filename, loc) - self.message_args = (missing_arguments,) - - - -@path pyflakes -""" -Provide the Reporter class. -""" - -import re -import sys - - -@others -@language python -@tabwidth -4 - -class Reporter: - """ - Formats the results of pyflakes checks to users. - """ - - @others - -def _makeDefaultReporter(): - """ - Make a reporter that can be used when no reporter is specified. - """ - return Reporter(sys.stdout, sys.stderr) - -def __init__(self, warningStream, errorStream): - """ - Construct a L{Reporter}. - - @param warningStream: A file-like object where warnings will be - written to. The stream's C{write} method must accept unicode. - C{sys.stdout} is a good value. - @param errorStream: A file-like object where error output will be - written to. The stream's C{write} method must accept unicode. - C{sys.stderr} is a good value. - """ - self._stdout = warningStream - self._stderr = errorStream - - -def unexpectedError(self, filename, msg): - """ - An unexpected error occurred trying to process C{filename}. - - @param filename: The path to a file that we could not process. - @ptype filename: C{unicode} - @param msg: A message explaining the problem. - @ptype msg: C{unicode} - """ - self._stderr.write(f"{filename}: {msg}\n") - - -def _must_match(regex, string, pos): - match = regex.match(string, pos) - assert match is not None - return match - - - -def syntaxError(self, filename, msg, lineno, offset, text): - """ - There was a syntax error in C{filename}. - - @param filename: The path to the file with the syntax error. - @ptype filename: C{unicode} - @param msg: An explanation of the syntax error. - @ptype msg: C{unicode} - @param lineno: The line number where the syntax error occurred. - @ptype lineno: C{int} - @param offset: The column on which the syntax error occurred, or None. - @ptype offset: C{int} - @param text: The source code containing the syntax error. - @ptype text: C{unicode} - """ - if text is None: - line = None - else: - line = text.splitlines()[-1] - - # lineno might be None if the error was during tokenization - # lineno might be 0 if the error came from stdin - lineno = max(lineno or 0, 1) - - if offset is not None: - # some versions of python emit an offset of -1 for certain encoding errors - offset = max(offset, 1) - self._stderr.write('%s:%d:%d: %s\n' % - (filename, lineno, offset, msg)) - else: - self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg)) - - if line is not None: - self._stderr.write(line) - self._stderr.write('\n') - if offset is not None: - self._stderr.write(re.sub(r'\S', ' ', line[:offset - 1]) + - "^\n") - - -def flake(self, message): - """ - pyflakes found something wrong with the code. - - @param: A L{pyflakes.messages.Message}. - """ - self._stdout.write(str(message)) - self._stdout.write('\n') - - - - -# Empty file. -@path pyflakes/test -import ast -import textwrap -import unittest - -from pyflakes import checker - -__all__ = ['TestCase', 'skip', 'skipIf'] - -skip = unittest.skip -skipIf = unittest.skipIf - - -@others -{}'''.format(input, expectedOutputs, '\n'.join([str(o) for o in w.messages]))) - return w -@language python -@tabwidth -4 - -class TestCase(unittest.TestCase): - -@others - - withDoctest = False - - def flakes(self, input, *expectedOutputs, **kw): - tree = ast.parse(textwrap.dedent(input)) - if kw.get('is_segment'): - tree = tree.body[0] - kw.pop('is_segment') - w = checker.Checker(tree, withDoctest=self.withDoctest, **kw) - outputs = [type(o) for o in w.messages] - expectedOutputs = list(expectedOutputs) - outputs.sort(key=lambda t: t.__name__) - expectedOutputs.sort(key=lambda t: t.__name__) - self.assertEqual(outputs, expectedOutputs, '''\ -for input: -{} -expected outputs: -{!r} -but got: - -def parse_percent_format(s): - """Parses the string component of a `'...' % ...` format call - - Copied from https://github.com/asottile/pyupgrade at v1.20.1 - """ - - def _parse_inner(): - string_start = 0 - string_end = 0 - in_fmt = False - - i = 0 - while i < len(s): - if not in_fmt: - try: - i = s.index('%', i) - except ValueError: # no more % fields! - yield s[string_start:], None - return - else: - string_end = i - i += 1 - in_fmt = True - else: - key_match = MAPPING_KEY_RE.match(s, i) - if key_match: - key = key_match.group(1) - i = key_match.end() - else: - key = None - - conversion_flag_match = _must_match(CONVERSION_FLAG_RE, s, i) - conversion_flag = conversion_flag_match.group() or None - i = conversion_flag_match.end() - - width_match = _must_match(WIDTH_RE, s, i) - width = width_match.group() or None - i = width_match.end() - - precision_match = _must_match(PRECISION_RE, s, i) - precision = precision_match.group() or None - i = precision_match.end() - - # length modifier is ignored - i = _must_match(LENGTH_RE, s, i).end() - - try: - conversion = s[i] - except IndexError: - raise ValueError('end-of-string while parsing format') - i += 1 - - fmt = (key, conversion_flag, width, precision, conversion) - yield s[string_start:string_end], fmt - - in_fmt = False - string_start = i - - if in_fmt: - raise ValueError('end-of-string while parsing format') - - return tuple(_parse_inner()) - - - -@path pyflakes/test -""" -Tests for L{pyflakes.scripts.pyflakes}. -""" - -import contextlib -import io -import os -import sys -import shutil -import subprocess -import tempfile - -from pyflakes.checker import PYPY -from pyflakes.messages import UnusedImport -from pyflakes.reporter import Reporter -from pyflakes.api import ( - main, - check, - checkPath, - checkRecursive, - iterSourceCode, -) -from pyflakes.test.harness import TestCase, skipIf - - -@others -@language python -@tabwidth -4 - -def withStderrTo(stderr, f, *args, **kwargs): - """ - Call C{f} with C{sys.stderr} redirected to C{stderr}. - """ - (outer, sys.stderr) = (sys.stderr, stderr) - try: - return f(*args, **kwargs) - finally: - sys.stderr = outer - - - -class Node: - """ - Mock an AST node. - """ - @others - -class SysStreamCapturing: - """Context manager capturing sys.stdin, sys.stdout and sys.stderr. - - The file handles are replaced with a StringIO object. - """ - - @others - -class LoggingReporter: - """ - Implementation of Reporter that just appends any error to a list. - """ - - @others - -class TestIterSourceCode(TestCase): - """ - Tests for L{iterSourceCode}. - """ - - @others - -class TestReporter(TestCase): - """ - Tests for L{Reporter}. - """ - - @others - -class CheckTests(TestCase): - """ - Tests for L{check} and L{checkPath} which check a file for flakes. - """ - -@others # Must have underindented strings. - - def test_nonDefaultFollowsDefaultSyntaxError(self): - """ - Source which has a non-default argument following a default argument - should include the line number of the syntax error. However these - exceptions do not include an offset. - """ - source = """\ -def foo(bar=baz, bax): - pass -""" - with self.makeTempFile(source) as sourcePath: - if sys.version_info >= (3, 12): - msg = 'parameter without a default follows parameter with a default' # noqa: E501 - else: - msg = 'non-default argument follows default argument' - - if PYPY and sys.version_info >= (3, 9): - column = 18 - elif PYPY: - column = 8 - elif sys.version_info >= (3, 10): - column = 18 - elif sys.version_info >= (3, 9): - column = 21 - else: - column = 9 - last_line = ' ' * (column - 1) + '^\n' - self.assertHasErrors( - sourcePath, - [f"""\ -{sourcePath}:1:{column}: {msg} -def foo(bar=baz, bax): -{last_line}"""] - ) - - - def test_nonKeywordAfterKeywordSyntaxError(self): - """ - Source which has a non-keyword argument after a keyword argument should - include the line number of the syntax error. However these exceptions - do not include an offset. - """ - source = """\ -foo(bar=baz, bax) -""" - with self.makeTempFile(source) as sourcePath: - if sys.version_info >= (3, 9): - column = 17 - elif not PYPY: - column = 14 - else: - column = 13 - last_line = ' ' * (column - 1) + '^\n' - columnstr = '%d:' % column - - message = 'positional argument follows keyword argument' - - self.assertHasErrors( - sourcePath, - ["""\ -{}:1:{} {} -foo(bar=baz, bax) -{}""".format(sourcePath, columnstr, message, last_line)]) - - -class _FieldsOrder(dict): - """Fix order of AST node fields.""" - - def _get_fields(self, node_class): - # handle iter before target, and generators before element - fields = node_class._fields - if 'iter' in fields: - key_first = 'iter'.find - elif 'generators' in fields: - key_first = 'generators'.find - else: - key_first = 'value'.find - return tuple(sorted(fields, key=key_first, reverse=True)) - - def __missing__(self, node_class): - self[node_class] = fields = self._get_fields(node_class) - return fields - - - - def test_invalidEscape(self): - """ - The invalid escape syntax raises ValueError in Python 2 - """ - # ValueError: invalid \x escape - with self.makeTempFile(r"foo = '\xyz'") as sourcePath: - position_end = 1 - if PYPY and sys.version_info >= (3, 9): - column = 7 - elif PYPY: - column = 6 - elif (3, 9) <= sys.version_info < (3, 12): - column = 13 - else: - column = 7 - - last_line = '%s^\n' % (' ' * (column - 1)) - - decoding_error = """\ -%s:1:%d: (unicode error) 'unicodeescape' codec can't decode bytes \ -in position 0-%d: truncated \\xXX escape -foo = '\\xyz' -%s""" % (sourcePath, column, position_end, last_line) - - self.assertHasErrors( - sourcePath, [decoding_error]) - - - @skipIf(sys.platform == 'win32', 'unsupported on Windows') - def test_permissionDenied(self): - """ - If the source file is not readable, this is reported on standard - error. - """ - if os.getuid() == 0: - self.skipTest('root user can access all files regardless of ' - 'permissions') - with self.makeTempFile('') as sourcePath: - os.chmod(sourcePath, 0) - count, errors = self.getErrors(sourcePath) - self.assertEqual(count, 1) - self.assertEqual( - errors, - [('unexpectedError', sourcePath, "Permission denied")]) - - - def test_pyflakesWarning(self): - """ - If the source file has a pyflakes warning, this is reported as a - 'flake'. - """ - with self.makeTempFile("import foo") as sourcePath: - count, errors = self.getErrors(sourcePath) - self.assertEqual(count, 1) - self.assertEqual( - errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))]) - - - def test_encodedFileUTF8(self): - """ - If source file declares the correct encoding, no error is reported. - """ - SNOWMAN = chr(0x2603) - source = ("""\ -# coding: utf-8 -x = "%s" -""" % SNOWMAN).encode('utf-8') - with self.makeTempFile(source) as sourcePath: - self.assertHasErrors(sourcePath, []) - - - def test_CRLFLineEndings(self): - """ - Source files with Windows CR LF line endings are parsed successfully. - """ - with self.makeTempFile("x = 42\r\n") as sourcePath: - self.assertHasErrors(sourcePath, []) - - - def test_misencodedFileUTF8(self): - """ - If a source file contains bytes which cannot be decoded, this is - reported on stderr. - """ - SNOWMAN = chr(0x2603) - source = ("""\ -# coding: ascii -x = "%s" -""" % SNOWMAN).encode('utf-8') - with self.makeTempFile(source) as sourcePath: - self.assertHasErrors( - sourcePath, - [f"{sourcePath}:1:1: 'ascii' codec can't decode byte 0xe2 in position 21: ordinal not in range(128)\n"]) # noqa: E501 - - - def test_misencodedFileUTF16(self): - """ - If a source file contains bytes which cannot be decoded, this is - reported on stderr. - """ - SNOWMAN = chr(0x2603) - source = ("""\ -# coding: ascii -x = "%s" -""" % SNOWMAN).encode('utf-16') - with self.makeTempFile(source) as sourcePath: - if sys.version_info < (3, 11, 4): - expected = f"{sourcePath}: problem decoding source\n" - else: - expected = f"{sourcePath}:1: source code string cannot contain null bytes\n" # noqa: E501 - - self.assertHasErrors(sourcePath, [expected]) - - def test_checkRecursive(self): - """ - L{checkRecursive} descends into each directory, finding Python files - and reporting problems. - """ - tempdir = tempfile.mkdtemp() - try: - os.mkdir(os.path.join(tempdir, 'foo')) - file1 = os.path.join(tempdir, 'foo', 'bar.py') - with open(file1, 'wb') as fd: - fd.write(b"import baz\n") - file2 = os.path.join(tempdir, 'baz.py') - with open(file2, 'wb') as fd: - fd.write(b"import contraband") - log = [] - reporter = LoggingReporter(log) - warnings = checkRecursive([tempdir], reporter) - self.assertEqual(warnings, 2) - self.assertEqual( - sorted(log), - sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))), - ('flake', - str(UnusedImport(file2, Node(1), 'contraband')))])) - finally: - shutil.rmtree(tempdir) - - def test_stdinReportsErrors(self): - """ - L{check} reports syntax errors from stdin - """ - source = "max(1 for i in range(10), key=lambda x: x+1)\n" - err = io.StringIO() - count = withStderrTo(err, check, source, "<stdin>") - self.assertEqual(count, 1) - errlines = err.getvalue().split("\n")[:-1] - - if sys.version_info >= (3, 9): - expected_error = [ - "<stdin>:1:5: Generator expression must be parenthesized", - "max(1 for i in range(10), key=lambda x: x+1)", - " ^", - ] - elif PYPY: - expected_error = [ - "<stdin>:1:4: Generator expression must be parenthesized if not sole argument", # noqa: E501 - "max(1 for i in range(10), key=lambda x: x+1)", - " ^", - ] - else: - expected_error = [ - "<stdin>:1:5: Generator expression must be parenthesized", - ] - - - self.assertEqual(errlines, expected_error) - - - - -class IntegrationTests(TestCase): - """ - Tests of the pyflakes script that actually spawn the script. - """ - @others - -def counter(items): - """ - Simplest required implementation of collections.Counter. Required as 2.6 - does not have Counter in collections. - """ - results = {} - for item in items: - results[item] = results.get(item, 0) + 1 - return results - - - -class TestMain(IntegrationTests): - """ - Tests of the pyflakes main function. - """ - @others - -def __init__(self, lineno, col_offset=0): - self.lineno = lineno - self.col_offset = col_offset - - - -def __init__(self, stdin): - self._stdin = io.StringIO(stdin or '', newline=os.linesep) - - -def __enter__(self): - self._orig_stdin = sys.stdin - self._orig_stdout = sys.stdout - self._orig_stderr = sys.stderr - - sys.stdin = self._stdin - sys.stdout = self._stdout_stringio = io.StringIO(newline=os.linesep) - sys.stderr = self._stderr_stringio = io.StringIO(newline=os.linesep) - - return self - - -def __exit__(self, *args): - self.output = self._stdout_stringio.getvalue() - self.error = self._stderr_stringio.getvalue() - - sys.stdin = self._orig_stdin - sys.stdout = self._orig_stdout - sys.stderr = self._orig_stderr - - - -def __init__(self, log): - """ - Construct a C{LoggingReporter}. - - @param log: A list to append log messages to. - """ - self.log = log - - -def flake(self, message): - self.log.append(('flake', str(message))) - - -def unexpectedError(self, filename, message): - self.log.append(('unexpectedError', filename, message)) - - -def syntaxError(self, filename, msg, lineno, offset, line): - self.log.append(('syntaxError', filename, msg, lineno, offset, line)) - - - -def setUp(self): - self.tempdir = tempfile.mkdtemp() - - -def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): - """ - Yield all direct child nodes of *node*, that is, all fields that - are nodes and all items of fields that are lists of nodes. - - :param node: AST node to be iterated upon - :param omit: String or tuple of strings denoting the - attributes of the node to be omitted from - further parsing - :param _fields_order: Order of AST node fields - """ - for name in _fields_order[node.__class__]: - if omit and name in omit: - continue - field = getattr(node, name, None) - if isinstance(field, ast.AST): - yield field - elif isinstance(field, list): - for item in field: - if isinstance(item, ast.AST): - yield item - - - -def tearDown(self): - shutil.rmtree(self.tempdir) - - -def makeEmptyFile(self, *parts): - assert parts - fpath = os.path.join(self.tempdir, *parts) - open(fpath, 'a').close() - return fpath - - -def test_emptyDirectory(self): - """ - There are no Python files in an empty directory. - """ - self.assertEqual(list(iterSourceCode([self.tempdir])), []) - - -def test_singleFile(self): - """ - If the directory contains one Python file, C{iterSourceCode} will find - it. - """ - childpath = self.makeEmptyFile('foo.py') - self.assertEqual(list(iterSourceCode([self.tempdir])), [childpath]) - - -def test_onlyPythonSource(self): - """ - Files that are not Python source files are not included. - """ - self.makeEmptyFile('foo.pyc') - self.assertEqual(list(iterSourceCode([self.tempdir])), []) - - -def test_recurses(self): - """ - If the Python files are hidden deep down in child directories, we will - find them. - """ - os.mkdir(os.path.join(self.tempdir, 'foo')) - apath = self.makeEmptyFile('foo', 'a.py') - self.makeEmptyFile('foo', 'a.py~') - os.mkdir(os.path.join(self.tempdir, 'bar')) - bpath = self.makeEmptyFile('bar', 'b.py') - cpath = self.makeEmptyFile('c.py') - self.assertEqual( - sorted(iterSourceCode([self.tempdir])), - sorted([apath, bpath, cpath])) - - -def test_shebang(self): - """ - Find Python files that don't end with `.py`, but contain a Python - shebang. - """ - python = os.path.join(self.tempdir, 'a') - with open(python, 'w') as fd: - fd.write('#!/usr/bin/env python\n') - - self.makeEmptyFile('b') - - with open(os.path.join(self.tempdir, 'c'), 'w') as fd: - fd.write('hello\nworld\n') - - python3 = os.path.join(self.tempdir, 'e') - with open(python3, 'w') as fd: - fd.write('#!/usr/bin/env python3\n') - - pythonw = os.path.join(self.tempdir, 'f') - with open(pythonw, 'w') as fd: - fd.write('#!/usr/bin/env pythonw\n') - - python3args = os.path.join(self.tempdir, 'g') - with open(python3args, 'w') as fd: - fd.write('#!/usr/bin/python3 -u\n') - - python3d = os.path.join(self.tempdir, 'i') - with open(python3d, 'w') as fd: - fd.write('#!/usr/local/bin/python3d\n') - - python38m = os.path.join(self.tempdir, 'j') - with open(python38m, 'w') as fd: - fd.write('#! /usr/bin/env python3.8m\n') - - # Should NOT be treated as Python source - notfirst = os.path.join(self.tempdir, 'l') - with open(notfirst, 'w') as fd: - fd.write('#!/bin/sh\n#!/usr/bin/python\n') - - self.assertEqual( - sorted(iterSourceCode([self.tempdir])), - sorted([ - python, python3, pythonw, python3args, python3d, - python38m, - ])) - - -def test_multipleDirectories(self): - """ - L{iterSourceCode} can be given multiple directories. It will recurse - into each of them. - """ - foopath = os.path.join(self.tempdir, 'foo') - barpath = os.path.join(self.tempdir, 'bar') - os.mkdir(foopath) - apath = self.makeEmptyFile('foo', 'a.py') - os.mkdir(barpath) - bpath = self.makeEmptyFile('bar', 'b.py') - self.assertEqual( - sorted(iterSourceCode([foopath, barpath])), - sorted([apath, bpath])) - - -def test_explicitFiles(self): - """ - If one of the paths given to L{iterSourceCode} is not a directory but - a file, it will include that in its output. - """ - epath = self.makeEmptyFile('e.py') - self.assertEqual(list(iterSourceCode([epath])), - [epath]) - - - -def test_syntaxError(self): - """ - C{syntaxError} reports that there was a syntax error in the source - file. It reports to the error stream and includes the filename, line - number, error message, actual line of source and a caret pointing to - where the error is. - """ - err = io.StringIO() - reporter = Reporter(None, err) - reporter.syntaxError('foo.py', 'a problem', 3, 8, 'bad line of source') - self.assertEqual( - ("foo.py:3:8: a problem\n" - "bad line of source\n" - " ^\n"), - err.getvalue()) - - -#!/usr/bin/env python -# Copyright 2005-2011 Divmod, Inc. -# Copyright 2013 Florent Xicluna. See LICENSE file for details -import os.path - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - extra = {'scripts': ["bin/pyflakes"]} -else: - extra = { - 'test_suite': 'pyflakes.test', - 'entry_points': { - 'console_scripts': ['pyflakes = pyflakes.api:main'], - }, - } - - -@others -setup( - name="pyflakes", - license="MIT", - version=get_version(), - description="passive checker of Python programs", - long_description=get_long_description(), - author="A lot of people", - author_email="code-quality@python.org", - url="https://github.com/PyCQA/pyflakes", - packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"], - python_requires='>=3.8', - classifiers=[ - "Development Status :: 6 - Mature", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Software Development", - "Topic :: Utilities", - ], - **extra) -@language python -@tabwidth -4 - -def convert_to_value(item): - if isinstance(item, ast.Constant): - return item.value - elif isinstance(item, ast.Tuple): - return tuple(convert_to_value(i) for i in item.elts) - elif isinstance(item, ast.Name): - return VariableKey(item=item) - else: - return UnhandledKeyType() - - - -def test_syntaxErrorNoOffset(self): - """ - C{syntaxError} doesn't include a caret pointing to the error if - C{offset} is passed as C{None}. - """ - err = io.StringIO() - reporter = Reporter(None, err) - reporter.syntaxError('foo.py', 'a problem', 3, None, - 'bad line of source') - self.assertEqual( - ("foo.py:3: a problem\n" - "bad line of source\n"), - err.getvalue()) - - -def test_syntaxErrorNoText(self): - """ - C{syntaxError} doesn't include text or nonsensical offsets if C{text} is C{None}. - - This typically happens when reporting syntax errors from stdin. - """ - err = io.StringIO() - reporter = Reporter(None, err) - reporter.syntaxError('<stdin>', 'a problem', 0, 0, None) - self.assertEqual(("<stdin>:1:1: a problem\n"), err.getvalue()) - - -def test_multiLineSyntaxError(self): - """ - If there's a multi-line syntax error, then we only report the last - line. The offset is adjusted so that it is relative to the start of - the last line. - """ - err = io.StringIO() - lines = [ - 'bad line of source', - 'more bad lines of source', - ] - reporter = Reporter(None, err) - reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7, - '\n'.join(lines)) - self.assertEqual( - ("foo.py:3:25: a problem\n" + - lines[-1] + "\n" + - " " * 24 + "^\n"), - err.getvalue()) - - -def test_unexpectedError(self): - """ - C{unexpectedError} reports an error processing a source file. - """ - err = io.StringIO() - reporter = Reporter(None, err) - reporter.unexpectedError('source.py', 'error message') - self.assertEqual('source.py: error message\n', err.getvalue()) - - -def test_flake(self): - """ - C{flake} reports a code warning from Pyflakes. It is exactly the - str() of a L{pyflakes.messages.Message}. - """ - out = io.StringIO() - reporter = Reporter(out, None) - message = UnusedImport('foo.py', Node(42), 'bar') - reporter.flake(message) - self.assertEqual(out.getvalue(), f"{message}\n") - - - - @contextlib.contextmanager - def makeTempFile(self, content): - """ - Make a temporary file containing C{content} and return a path to it. - """ - fd, name = tempfile.mkstemp() - try: - with os.fdopen(fd, 'wb') as f: - if not hasattr(content, 'decode'): - content = content.encode('ascii') - f.write(content) - yield name - finally: - os.remove(name) - - - def assertHasErrors(self, path, errorList): - """ - Assert that C{path} causes errors. - - @param path: A path to a file to check. - @param errorList: A list of errors expected to be printed to stderr. - """ - err = io.StringIO() - count = withStderrTo(err, checkPath, path) - self.assertEqual( - (count, err.getvalue()), (len(errorList), ''.join(errorList))) - - - def getErrors(self, path): - """ - Get any warnings or errors reported by pyflakes for the file at C{path}. - - @param path: The path to a Python file on disk that pyflakes will check. - @return: C{(count, log)}, where C{count} is the number of warnings or - errors generated, and log is a list of those warnings, presented - as structured data. See L{LoggingReporter} for more details. - """ - log = [] - reporter = LoggingReporter(log) - count = checkPath(path, reporter) - return count, log - - - def test_legacyScript(self): - from pyflakes.scripts import pyflakes as script_pyflakes - self.assertIs(script_pyflakes.checkPath, checkPath) - - - def test_missingTrailingNewline(self): - """ - Source which doesn't end with a newline shouldn't cause any - exception to be raised nor an error indicator to be returned by - L{check}. - """ - with self.makeTempFile("def foo():\n\tpass\n\t") as fName: - self.assertHasErrors(fName, []) - - -def is_notimplemented_name_node(node): - return isinstance(node, ast.Name) and getNodeName(node) == 'NotImplemented' - - - - def test_checkPathNonExisting(self): - """ - L{checkPath} handles non-existing files. - """ - count, errors = self.getErrors('extremo') - self.assertEqual(count, 1) - self.assertEqual( - errors, - [('unexpectedError', 'extremo', 'No such file or directory')]) - - - def test_multilineSyntaxError(self): - """ - Source which includes a syntax error which results in the raised - L{SyntaxError.text} containing multiple lines of source are reported - with only the last line of that source. - """ - source = """\ -def foo(): - ''' - -def bar(): - pass - -def baz(): - '''quux''' -""" - - # Sanity check - SyntaxError.text should be multiple lines, if it - # isn't, something this test was unprepared for has happened. - def evaluate(source): - exec(source) - try: - evaluate(source) - except SyntaxError as e: - if not PYPY and sys.version_info < (3, 10): - self.assertTrue(e.text.count('\n') > 1) - else: - self.fail() - - with self.makeTempFile(source) as sourcePath: - if PYPY: - message = 'end of file (EOF) while scanning triple-quoted string literal' - elif sys.version_info >= (3, 10): - message = 'unterminated triple-quoted string literal (detected at line 8)' # noqa: E501 - else: - message = 'invalid syntax' - - if PYPY or sys.version_info >= (3, 10): - column = 12 - else: - column = 8 - self.assertHasErrors( - sourcePath, - ["""\ -%s:8:%d: %s - '''quux''' -%s^ -""" % (sourcePath, column, message, ' ' * (column - 1))]) - - def test_eofSyntaxError(self): - """ - The error reported for source files which end prematurely causing a - syntax error reflects the cause for the syntax error. - """ - with self.makeTempFile("def foo(") as sourcePath: - - if PYPY: - msg = 'parenthesis is never closed' - elif sys.version_info >= (3, 10): - msg = "'(' was never closed" - else: - msg = 'unexpected EOF while parsing' - - if PYPY or sys.version_info >= (3, 10): - column = 8 - else: - column = 9 - - spaces = ' ' * (column - 1) - expected = '{}:1:{}: {}\ndef foo(\n{}^\n'.format( - sourcePath, column, msg, spaces - ) - - self.assertHasErrors(sourcePath, [expected]) - - - def test_eofSyntaxErrorWithTab(self): - """ - The error reported for source files which end prematurely causing a - syntax error reflects the cause for the syntax error. - """ - with self.makeTempFile("if True:\n\tfoo =") as sourcePath: - self.assertHasErrors( - sourcePath, - [f"""\ -{sourcePath}:2:7: invalid syntax -\tfoo = -\t ^ -"""]) - - -def setUp(self): - self.tempdir = tempfile.mkdtemp() - self.tempfilepath = os.path.join(self.tempdir, 'temp') - - -def tearDown(self): - shutil.rmtree(self.tempdir) - - -def getPyflakesBinary(self): - """ - Return the path to the pyflakes binary. - """ - import pyflakes - package_dir = os.path.dirname(pyflakes.__file__) - return os.path.join(package_dir, '..', 'bin', 'pyflakes') - - -def runPyflakes(self, paths, stdin=None): - """ - Launch a subprocess running C{pyflakes}. - - @param paths: Command-line arguments to pass to pyflakes. - @param stdin: Text to use as stdin. - @return: C{(returncode, stdout, stderr)} of the completed pyflakes - process. - """ - env = dict(os.environ) - env['PYTHONPATH'] = os.pathsep.join(sys.path) - command = [sys.executable, self.getPyflakesBinary()] - command.extend(paths) - if stdin: - p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = p.communicate(stdin.encode('ascii')) - else: - p = subprocess.Popen(command, env=env, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = p.communicate() - rv = p.wait() - stdout = stdout.decode('utf-8') - stderr = stderr.decode('utf-8') - return (stdout, stderr, rv) - - -def test_goodFile(self): - """ - When a Python source file is all good, the return code is zero and no - messages are printed to either stdout or stderr. - """ - open(self.tempfilepath, 'a').close() - d = self.runPyflakes([self.tempfilepath]) - self.assertEqual(d, ('', '', 0)) - - -def test_fileWithFlakes(self): - """ - When a Python source file has warnings, the return code is non-zero - and the warnings are printed to stdout. - """ - with open(self.tempfilepath, 'wb') as fd: - fd.write(b"import contraband\n") - d = self.runPyflakes([self.tempfilepath]) - expected = UnusedImport(self.tempfilepath, Node(1), 'contraband') - self.assertEqual(d, (f"{expected}{os.linesep}", '', 1)) - - -class Binding: - """ - Represents the binding of a value to a name. - - The checker uses this to keep track of which names have been bound and - which names have not. See L{Assignment} for a special type of binding that - is checked with stricter rules. - - @ivar used: pair of (L{Scope}, node) indicating the scope and - the node that this binding was last used. - """ - - def __init__(self, name, source): - self.name = name - self.source = source - self.used = False - - def __str__(self): - return self.name - - def __repr__(self): - return '<{} object {!r} from line {!r} at 0x{:x}>'.format( - self.__class__.__name__, - self.name, - self.source.lineno, - id(self), - ) - - def redefines(self, other): - return isinstance(other, Definition) and self.name == other.name - - - -def test_errors_io(self): - """ - When pyflakes finds errors with the files it's given, (if they don't - exist, say), then the return code is non-zero and the errors are - printed to stderr. - """ - d = self.runPyflakes([self.tempfilepath]) - error_msg = '{}: No such file or directory{}'.format(self.tempfilepath, - os.linesep) - self.assertEqual(d, ('', error_msg, 1)) - - -def test_errors_syntax(self): - """ - When pyflakes finds errors with the files it's given, (if they don't - exist, say), then the return code is non-zero and the errors are - printed to stderr. - """ - with open(self.tempfilepath, 'wb') as fd: - fd.write(b"import") - d = self.runPyflakes([self.tempfilepath]) - error_msg = '{0}:1:7: invalid syntax{1}import{1} ^{1}'.format( - self.tempfilepath, os.linesep) - self.assertEqual(d, ('', error_msg, 1)) - - -def test_readFromStdin(self): - """ - If no arguments are passed to C{pyflakes} then it reads from stdin. - """ - d = self.runPyflakes([], stdin='import contraband') - expected = UnusedImport('<stdin>', Node(1), 'contraband') - self.assertEqual(d, (f"{expected}{os.linesep}", '', 1)) - - - -def runPyflakes(self, paths, stdin=None): - try: - with SysStreamCapturing(stdin) as capture: - main(args=paths) - except SystemExit as e: - self.assertIsInstance(e.code, bool) - rv = int(e.code) - return (capture.output, capture.error, rv) - else: - raise RuntimeError('SystemExit not raised') - -@path pyflakes/test -""" -Tests for detecting redefinition of builtins. -""" -from pyflakes import messages as m -from pyflakes.test.harness import TestCase - - -@others - ''') -@language python -@tabwidth -4 - -class TestBuiltins(TestCase): - - @others - - ''', m.UndefinedLocal) - - def test_global_shadowing_builtin(self): - self.flakes(''' - def f(): - global range - range = None - print(range) - - f() - -def test_builtin_unbound_local(self): - self.flakes(''' - def foo(): - a = range(1, 10) - range = a - return range - - foo() - - print(range) - -@path pyflakes/test -from pyflakes import messages as m -from pyflakes.checker import (FunctionScope, ClassScope, ModuleScope, - Argument, FunctionDefinition, Assignment) -from pyflakes.test.harness import TestCase - - -@others -@language python -@tabwidth -4 - -class TestCodeSegments(TestCase): - """ - Tests for segments of a module - """ - - @others - -class Definition(Binding): - """ - A binding that defines a function or a class. - """ - @others - - ''', is_segment=True) - - self.flakes(''' - def foo(): - def bar(): - x = 0 - ''', m.UnusedVariable, is_segment=True) - - def test_class_segment(self): - self.flakes(''' - class Foo: - class Bar: - pass - - ''', is_segment=True) - - self.flakes(''' - class Foo: - def bar(): - x = 0 - ''', m.UnusedVariable, is_segment=True) - - def test_scope_class(self): - checker = self.flakes(''' - class Foo: - x = 0 - def bar(a, b=1, *d, **e): - pass - - ''', is_segment=True) - - scopes = checker.deadScopes - module_scopes = [ - scope for scope in scopes if scope.__class__ is ModuleScope] - class_scopes = [ - scope for scope in scopes if scope.__class__ is ClassScope] - function_scopes = [ - scope for scope in scopes if scope.__class__ is FunctionScope] - - # Ensure module scope is not present because we are analysing - # the inner contents of Foo - self.assertEqual(len(module_scopes), 0) - self.assertEqual(len(class_scopes), 1) - self.assertEqual(len(function_scopes), 1) - - class_scope = class_scopes[0] - function_scope = function_scopes[0] - - self.assertIsInstance(class_scope, ClassScope) - self.assertIsInstance(function_scope, FunctionScope) - - self.assertIn('x', class_scope) - self.assertIn('bar', class_scope) - - self.assertIn('a', function_scope) - self.assertIn('b', function_scope) - self.assertIn('d', function_scope) - self.assertIn('e', function_scope) - - self.assertIsInstance(class_scope['bar'], FunctionDefinition) - self.assertIsInstance(class_scope['x'], Assignment) - - self.assertIsInstance(function_scope['a'], Argument) - self.assertIsInstance(function_scope['b'], Argument) - self.assertIsInstance(function_scope['d'], Argument) - self.assertIsInstance(function_scope['e'], Argument) - - def test_scope_function(self): - checker = self.flakes(''' - def foo(a, b=1, *d, **e): - def bar(f, g=1, *h, **i): - pass - - ''', is_segment=True) - - scopes = checker.deadScopes - module_scopes = [ - scope for scope in scopes if scope.__class__ is ModuleScope] - function_scopes = [ - scope for scope in scopes if scope.__class__ is FunctionScope] - - # Ensure module scope is not present because we are analysing - # the inner contents of foo - self.assertEqual(len(module_scopes), 0) - self.assertEqual(len(function_scopes), 2) - - function_scope_foo = function_scopes[1] - function_scope_bar = function_scopes[0] - - self.assertIsInstance(function_scope_foo, FunctionScope) - self.assertIsInstance(function_scope_bar, FunctionScope) - - self.assertIn('a', function_scope_foo) - self.assertIn('b', function_scope_foo) - self.assertIn('d', function_scope_foo) - self.assertIn('e', function_scope_foo) - self.assertIn('bar', function_scope_foo) - - self.assertIn('f', function_scope_bar) - self.assertIn('g', function_scope_bar) - self.assertIn('h', function_scope_bar) - self.assertIn('i', function_scope_bar) - - self.assertIsInstance(function_scope_foo['bar'], FunctionDefinition) - self.assertIsInstance(function_scope_foo['a'], Argument) - self.assertIsInstance(function_scope_foo['b'], Argument) - self.assertIsInstance(function_scope_foo['d'], Argument) - self.assertIsInstance(function_scope_foo['e'], Argument) - - self.assertIsInstance(function_scope_bar['f'], Argument) - self.assertIsInstance(function_scope_bar['g'], Argument) - self.assertIsInstance(function_scope_bar['h'], Argument) - self.assertIsInstance(function_scope_bar['i'], Argument) - - def test_scope_async_function(self): - self.flakes('async def foo(): pass', is_segment=True) - -def test_function_segment(self): - self.flakes(''' - def foo(): - def bar(): - pass - -@path pyflakes/test -""" -Tests for dict duplicate keys Pyflakes behavior. -""" - -from pyflakes import messages as m -from pyflakes.test.harness import TestCase - - -@others - ''') -@language python -@tabwidth -4 - -class Test(TestCase): - - @others - - ''', - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - def test_duplicate_keys_in_lambda(self): - self.flakes( - "lambda x: {(0,1): 1, (0,1): 2}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - - def test_duplicate_keys_tuples(self): - self.flakes( - "{(0,1): 1, (0,1): 2}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - - def test_duplicate_keys_tuples_int_and_float(self): - self.flakes( - "{(0,1): 1, (0,1.0): 2}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - -class Builtin(Definition): - """A definition created for all Python builtins.""" - - @others - - def test_duplicate_keys_ints(self): - self.flakes( - "{1: 1, 1: 2}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - - def test_duplicate_keys_bools(self): - self.flakes( - "{True: 1, True: 2}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - - def test_duplicate_keys_bools_false(self): - # Needed to ensure 2.x correctly coerces these from variables - self.flakes( - "{False: 1, False: 2}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - - def test_duplicate_keys_none(self): - self.flakes( - "{None: 1, None: 2}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - - def test_duplicate_variable_keys(self): - self.flakes( - ''' - a = 1 - {a: 1, a: 2} - - ''', - m.MultiValueRepeatedKeyVariable, - m.MultiValueRepeatedKeyVariable, - ) - - def test_duplicate_variable_values(self): - self.flakes( - ''' - a = 1 - b = 2 - {1: a, 1: b} - - ''', - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - def test_duplicate_variable_values_same_value(self): - # Current behaviour is not to look up variable values. This is to - # confirm that. - self.flakes( - ''' - a = 1 - b = 1 - {1: a, 1: b} - - ''', - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - def test_duplicate_key_float_and_int(self): - """ - These do look like different values, but when it comes to their use as - keys, they compare as equal and so are actually duplicates. - The literal dict {1: 1, 1.0: 1} actually becomes {1.0: 1}. - """ - self.flakes( - ''' - {1: 1, 1.0: 2} - - ''', - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - def test_no_duplicate_key_error_same_value(self): - self.flakes(''' - {'yes': 1, 'yes': 1} - - ''') - - def test_no_duplicate_key_errors(self): - self.flakes(''' - {'yes': 1, 'no': 2} - -class UnhandledKeyType: - """ - A dictionary key of a type that we cannot or do not check for duplicates. - """ - - - - ''') - - def test_no_duplicate_keys_tuples_same_first_element(self): - self.flakes("{(0,1): 1, (0,2): 1}") - - - def test_no_duplicate_key_errors_func_call(self): - self.flakes(''' - def test(thing): - pass - test({True: 1, None: 2, False: 1}) - - ''') - - def test_no_duplicate_key_errors_bool_or_none(self): - self.flakes("{True: 1, None: 2, False: 1}") - - - def test_no_duplicate_key_errors_ints(self): - self.flakes(''' - {1: 1, 2: 1} - - ''') - - def test_no_duplicate_key_errors_vars(self): - self.flakes(''' - test = 'yes' - rest = 'yes' - {test: 1, rest: 2} - - ''') - - def test_no_duplicate_key_errors_tuples(self): - self.flakes(''' - {(0,1): 1, (0,2): 1} - - ''') - - def test_no_duplicate_key_errors_instance_attributes(self): - self.flakes(''' - class Test(): - pass - f = Test() - f.a = 1 - {f.a: 1, f.a: 1} - -def test_duplicate_keys(self): - self.flakes( - "{'yes': 1, 'yes': 2}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - -def test_duplicate_keys_bytes_vs_unicode_py3(self): - self.flakes("{b'a': 1, u'a': 2}") - - -def test_duplicate_values_bytes_vs_unicode_py3(self): - self.flakes( - "{1: b'a', 1: u'a'}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - -class VariableKey: - """ - A dictionary key which is a variable. - - @ivar item: The variable AST object. - """ - @others - -def test_multiple_duplicate_keys(self): - self.flakes( - "{'yes': 1, 'yes': 2, 'no': 2, 'no': 3}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - -def test_duplicate_keys_in_function(self): - self.flakes( - ''' - def f(thing): - pass - f({'yes': 1, 'yes': 2}) - -@path pyflakes/test -import textwrap - -from pyflakes import messages as m -from pyflakes.checker import ( - PYPY, - DoctestScope, - FunctionScope, - ModuleScope, -) -from pyflakes.test.test_other import Test as TestOther -from pyflakes.test.test_imports import Test as TestImports -from pyflakes.test.test_undefined_names import Test as TestUndefinedNames -from pyflakes.test.harness import TestCase, skip - - -@others -@language python -@tabwidth -4 - -class _DoctestMixin: - - @others - - ''') - return doctestificator % "\n ".join(lines) - - def flakes(self, input, *args, **kw): - return super().flakes(self.doctestify(input), *args, **kw) - - - -class Test(TestCase): - - @others - - """) - - scopes = checker.deadScopes - module_scopes = [ - scope for scope in scopes if scope.__class__ is ModuleScope] - doctest_scopes = [ - scope for scope in scopes if scope.__class__ is DoctestScope] - function_scopes = [ - scope for scope in scopes if scope.__class__ is FunctionScope] - - self.assertEqual(len(module_scopes), 1) - self.assertEqual(len(doctest_scopes), 1) - - module_scope = module_scopes[0] - doctest_scope = doctest_scopes[0] - - self.assertIsInstance(doctest_scope, DoctestScope) - self.assertIsInstance(doctest_scope, ModuleScope) - self.assertNotIsInstance(doctest_scope, FunctionScope) - self.assertNotIsInstance(module_scope, DoctestScope) - - self.assertIn('m', module_scope) - self.assertIn('doctest_stuff', module_scope) - - self.assertIn('d', doctest_scope) - - self.assertEqual(len(function_scopes), 1) - self.assertIn('f', function_scopes[0]) - - def test_nested_doctest_ignored(self): - """Check that nested doctests are ignored.""" - checker = self.flakes(""" - m = None - - def doctest_stuff(): - ''' - >>> def function_in_doctest(): - ... \"\"\" - ... >>> ignored_undefined_name - ... \"\"\" - ... df = m - ... return df - ... - >>> function_in_doctest() - ''' - f = m - return f - - """) - - scopes = checker.deadScopes - module_scopes = [ - scope for scope in scopes if scope.__class__ is ModuleScope] - doctest_scopes = [ - scope for scope in scopes if scope.__class__ is DoctestScope] - function_scopes = [ - scope for scope in scopes if scope.__class__ is FunctionScope] - - self.assertEqual(len(module_scopes), 1) - self.assertEqual(len(doctest_scopes), 1) - - module_scope = module_scopes[0] - doctest_scope = doctest_scopes[0] - - self.assertIn('m', module_scope) - self.assertIn('doctest_stuff', module_scope) - self.assertIn('function_in_doctest', doctest_scope) - - self.assertEqual(len(function_scopes), 2) - - self.assertIn('f', function_scopes[0]) - self.assertIn('df', function_scopes[1]) - - def test_global_module_scope_pollution(self): - """Check that global in doctest does not pollute module scope.""" - checker = self.flakes(""" - def doctest_stuff(): - ''' - >>> def function_in_doctest(): - ... global m - ... m = 50 - ... df = 10 - ... m = df - ... - >>> function_in_doctest() - ''' - f = 10 - return f - - - """) - - scopes = checker.deadScopes - module_scopes = [ - scope for scope in scopes if scope.__class__ is ModuleScope] - doctest_scopes = [ - scope for scope in scopes if scope.__class__ is DoctestScope] - function_scopes = [ - scope for scope in scopes if scope.__class__ is FunctionScope] - - self.assertEqual(len(module_scopes), 1) - self.assertEqual(len(doctest_scopes), 1) - - module_scope = module_scopes[0] - doctest_scope = doctest_scopes[0] - - self.assertIn('doctest_stuff', module_scope) - self.assertIn('function_in_doctest', doctest_scope) - - self.assertEqual(len(function_scopes), 2) - - self.assertIn('f', function_scopes[0]) - self.assertIn('df', function_scopes[1]) - self.assertIn('m', function_scopes[1]) - - self.assertNotIn('m', module_scope) - - def test_global_undefined(self): - self.flakes(""" - global m - - def doctest_stuff(): - ''' - >>> m - ''' - - """, m.UndefinedName) - - def test_nested_class(self): - """Doctest within nested class are processed.""" - self.flakes(""" - class C: - class D: - ''' - >>> m - ''' - def doctest_stuff(self): - ''' - >>> m - ''' - return 1 - -class Importation(Definition): - """ - A binding created by an import statement. - - @ivar fullName: The complete name given to the import statement, - possibly including multiple dotted components. - @type fullName: C{str} - """ - - @others - - """, m.UndefinedName, m.UndefinedName) - - def test_ignore_nested_function(self): - """Doctest module does not process doctest in nested functions.""" - # 'syntax error' would cause a SyntaxError if the doctest was processed. - # However doctest does not find doctest in nested functions - # (https://bugs.python.org/issue1650090). If nested functions were - # processed, this use of m should cause UndefinedName, and the - # name inner_function should probably exist in the doctest scope. - self.flakes(""" - def doctest_stuff(): - def inner_function(): - ''' - >>> syntax error - >>> inner_function() - 1 - >>> m - ''' - return 1 - m = inner_function() - return m - - """) - - def test_inaccessible_scope_class(self): - """Doctest may not access class scope.""" - self.flakes(""" - class C: - def doctest_stuff(self): - ''' - >>> m - ''' - return 1 - m = 1 - - """, m.UndefinedName) - - def test_importBeforeDoctest(self): - self.flakes(""" - import foo - - def doctest_stuff(): - ''' - >>> foo - ''' - - """) - - @skip("todo") - def test_importBeforeAndInDoctest(self): - self.flakes(''' - import foo - - def doctest_stuff(): - """ - >>> import foo - >>> foo - """ - - foo - - ''', m.RedefinedWhileUnused) - - def test_importInDoctestAndAfter(self): - self.flakes(''' - def doctest_stuff(): - """ - >>> import foo - >>> foo - """ - - import foo - foo() - - ''') - - def test_offsetInDoctests(self): - exc = self.flakes(''' - - def doctest_stuff(): - """ - >>> x # line 5 - """ - - - ''', m.UndefinedName).messages[0] - self.assertEqual(exc.lineno, 5) - self.assertEqual(exc.col, 12) - - def test_offsetInLambdasInDoctests(self): - exc = self.flakes(''' - - def doctest_stuff(): - """ - >>> lambda: x # line 5 - """ - - - ''', m.UndefinedName).messages[0] - self.assertEqual(exc.lineno, 5) - self.assertEqual(exc.col, 20) - - def test_offsetAfterDoctests(self): - exc = self.flakes(''' - - def doctest_stuff(): - """ - >>> x = 5 - """ - - x - - - ''', m.UndefinedName).messages[0] - self.assertEqual(exc.lineno, 8) - self.assertEqual(exc.col, 0) - - def test_syntaxErrorInDoctest(self): - exceptions = self.flakes( - ''' - def doctest_stuff(): - """ - >>> from # line 4 - >>> fortytwo = 42 - >>> except Exception: - """ - - ''', - m.DoctestSyntaxError, - m.DoctestSyntaxError, - m.DoctestSyntaxError).messages - exc = exceptions[0] - self.assertEqual(exc.lineno, 4) - if not PYPY: - self.assertEqual(exc.col, 18) - else: - self.assertEqual(exc.col, 26) - - # PyPy error column offset is 0, - # for the second and third line of the doctest - # i.e. at the beginning of the line - exc = exceptions[1] - self.assertEqual(exc.lineno, 5) - if PYPY: - self.assertEqual(exc.col, 13) - else: - self.assertEqual(exc.col, 16) - exc = exceptions[2] - self.assertEqual(exc.lineno, 6) - self.assertEqual(exc.col, 13) - - def test_indentationErrorInDoctest(self): - exc = self.flakes(''' - def doctest_stuff(): - """ - >>> if True: - ... pass - """ - -class SubmoduleImportation(Importation): - """ - A binding created by a submodule import statement. - - A submodule import is a special case where the root module is implicitly - imported, without an 'as' clause, and the submodule is also imported. - Python does not restrict which attributes of the root module may be used. - - This class is only used when the submodule import is without an 'as' clause. - - pyflakes handles this case by registering the root module name in the scope, - allowing any attribute of the root module to be accessed. - - RedefinedWhileUnused is suppressed in `redefines` unless the submodule - name is also the same, to avoid false positives. - """ - - @others - - ''', m.DoctestSyntaxError).messages[0] - self.assertEqual(exc.lineno, 5) - self.assertEqual(exc.col, 13) - - def test_offsetWithMultiLineArgs(self): - (exc1, exc2) = self.flakes( - ''' - def doctest_stuff(arg1, - arg2, - arg3): - """ - >>> assert - >>> this - """ - - ''', - m.DoctestSyntaxError, - m.UndefinedName).messages - self.assertEqual(exc1.lineno, 6) - self.assertEqual(exc1.col, 19) - self.assertEqual(exc2.lineno, 7) - self.assertEqual(exc2.col, 12) - - def test_doctestCanReferToFunction(self): - self.flakes(""" - def foo(): - ''' - >>> foo - ''' - - """) - - def test_doctestCanReferToClass(self): - self.flakes(""" - class Foo(): - ''' - >>> Foo - ''' - def bar(self): - ''' - >>> Foo - ''' - - """) - - def test_noOffsetSyntaxErrorInDoctest(self): - exceptions = self.flakes( - ''' - def buildurl(base, *args, **kwargs): - """ - >>> buildurl('/blah.php', ('a', '&'), ('b', '=') - '/blah.php?a=%26&b=%3D' - >>> buildurl('/blah.php', a='&', 'b'='=') - '/blah.php?b=%3D&a=%26' - """ - pass - - ''', - m.DoctestSyntaxError, - m.DoctestSyntaxError).messages - exc = exceptions[0] - self.assertEqual(exc.lineno, 4) - exc = exceptions[1] - self.assertEqual(exc.lineno, 6) - - def test_singleUnderscoreInDoctest(self): - self.flakes(''' - def func(): - """A docstring - - >>> func() - 1 - >>> _ - 1 - """ - return 1 - - ''') - - def test_globalUnderscoreInDoctest(self): - self.flakes(""" - from gettext import ugettext as _ - - def doctest_stuff(): - ''' - >>> pass - ''' - - """, m.UnusedImport) - - -class TestOther(_DoctestMixin, TestOther): - """Run TestOther with each test wrapped in a doctest.""" - - - -class TestImports(_DoctestMixin, TestImports): - """Run TestImports with each test wrapped in a doctest.""" - - - -class TestUndefinedNames(_DoctestMixin, TestUndefinedNames): - """Run TestUndefinedNames with each test wrapped in a doctest.""" - -withDoctest = True - -def doctestify(self, input): - lines = [] - for line in textwrap.dedent(input).splitlines(): - if line.strip() == '': - pass - elif (line.startswith(' ') or - line.startswith('except:') or - line.startswith('except ') or - line.startswith('finally:') or - line.startswith('else:') or - line.startswith('elif ') or - (lines and lines[-1].startswith(('>>> @', '... @')))): - line = "... %s" % line - else: - line = ">>> %s" % line - lines.append(line) - doctestificator = textwrap.dedent('''\ - def doctest_something(): - """ - %s - """ - -class ImportationFrom(Importation): - - @others - -withDoctest = True - -def test_scope_class(self): - """Check that a doctest is given a DoctestScope.""" - checker = self.flakes(""" - m = None - - def doctest_stuff(): - ''' - >>> d = doctest_stuff() - ''' - f = m - return f - -@path pyflakes/test -from pyflakes import messages as m -from pyflakes.checker import ( - FutureImportation, - Importation, - ImportationFrom, - StarImportation, - SubmoduleImportation, -) -from pyflakes.test.harness import TestCase, skip - - -@others - ''') - - self.flakes(''' - from interior import decorate - @decorate("foo") - class bar: - pass - ''') - - self.flakes(''' - @decorate - class foo: - pass - ''', m.UndefinedName) -@language python -@tabwidth -4 - -class TestImportationObject(TestCase): - - @others - -class Test(TestCase): - - @others - - os.path''', m.RedefinedWhileUnused) - - def test_redefinedIfElse(self): - """ - Test that importing a module twice in if - and else blocks does not raise a warning. - """ - self.flakes(''' - i = 2 - if i==1: - import os - else: - import os - - os.path''') - - def test_redefinedTry(self): - """ - Test that importing a module twice in a try block - does raise a warning. - """ - self.flakes(''' - try: - import os - import os - except: - pass - - os.path''', m.RedefinedWhileUnused) - - def test_redefinedTryExcept(self): - """ - Test that importing a module twice in a try - and except block does not raise a warning. - """ - self.flakes(''' - try: - import os - except: - import os - - os.path''') - - def test_redefinedTryNested(self): - """ - Test that importing a module twice using a nested - try/except and if blocks does not issue a warning. - """ - self.flakes(''' - try: - if True: - if True: - import os - except: - import os - - os.path''') - - def test_redefinedTryExceptMulti(self): - self.flakes(""" - try: - from aa import mixer - except AttributeError: - from bb import mixer - except RuntimeError: - from cc import mixer - except: - from dd import mixer - mixer(123) - - """) - - def test_redefinedTryElse(self): - self.flakes(""" - try: - from aa import mixer - except ImportError: - pass - else: - from bb import mixer - mixer(123) - -def get_version(fname=os.path.join('pyflakes', '__init__.py')): - with open(fname) as f: - for line in f: - if line.startswith('__version__'): - return eval(line.split('=')[-1]) - - - -class StarImportation(Importation): - """A binding created by a 'from x import *' statement.""" - - @others - - """, m.RedefinedWhileUnused) - - def test_redefinedTryExceptElse(self): - self.flakes(""" - try: - import funca - except ImportError: - from bb import funca - from bb import funcb - else: - from bbb import funcb - print(funca, funcb) - - """) - - def test_redefinedTryExceptFinally(self): - self.flakes(""" - try: - from aa import a - except ImportError: - from bb import a - finally: - a = 42 - print(a) - - """) - - def test_redefinedTryExceptElseFinally(self): - self.flakes(""" - try: - import b - except ImportError: - b = Ellipsis - from bb import a - else: - from aa import a - finally: - a = 42 - print(a, b) - - """) - - def test_redefinedByFunction(self): - self.flakes(''' - import fu - def fu(): - pass - - ''', m.RedefinedWhileUnused) - - def test_redefinedInNestedFunction(self): - """ - Test that shadowing a global name with a nested function definition - generates a warning. - """ - self.flakes(''' - import fu - def bar(): - def baz(): - def fu(): - pass - - ''', m.RedefinedWhileUnused, m.UnusedImport) - - def test_redefinedInNestedFunctionTwice(self): - """ - Test that shadowing a global name with a nested function definition - generates a warning. - """ - self.flakes(''' - import fu - def bar(): - import fu - def baz(): - def fu(): - pass - - ''', - m.RedefinedWhileUnused, m.RedefinedWhileUnused, - m.UnusedImport, m.UnusedImport) - - def test_redefinedButUsedLater(self): - """ - Test that a global import which is redefined locally, - but used later in another scope does not generate a warning. - """ - self.flakes(''' - import unittest, transport - - class GetTransportTestCase(unittest.TestCase): - def test_get_transport(self): - transport = 'transport' - self.assertIsNotNone(transport) - - class TestTransportMethodArgs(unittest.TestCase): - def test_send_defaults(self): - transport.Transport() - - ''') - - def test_redefinedByClass(self): - self.flakes(''' - import fu - class fu: - pass - - ''', m.RedefinedWhileUnused) - - def test_redefinedBySubclass(self): - """ - If an imported name is redefined by a class statement which also uses - that name in the bases list, no warning is emitted. - """ - self.flakes(''' - from fu import bar - class bar(bar): - pass - - ''') - - def test_redefinedInClass(self): - """ - Test that shadowing a global with a class attribute does not produce a - warning. - """ - self.flakes(''' - import fu - class bar: - fu = 1 - print(fu) - -class FutureImportation(ImportationFrom): - """ - A binding created by a from `__future__` import statement. - - `__future__` imports are implicitly used. - """ - - @others - - ''') - - def test_importInClass(self): - """ - Test that import within class is a locally scoped attribute. - """ - self.flakes(''' - class bar: - import fu - - ''') - - self.flakes(''' - class bar: - import fu - - fu - ''', m.UndefinedName) - - def test_usedInFunction(self): - self.flakes(''' - import fu - def fun(): - print(fu) - - ''') - - def test_shadowedByParameter(self): - self.flakes(''' - import fu - def fun(fu): - print(fu) - - ''', m.UnusedImport, m.RedefinedWhileUnused) - - self.flakes(''' - import fu - def fun(fu): - print(fu) - print(fu) - ''') - - def test_newAssignment(self): - self.flakes('fu = None') - - - def test_usedInGetattr(self): - self.flakes('import fu; fu.bar.baz') - self.flakes('import fu; "bar".fu.baz', m.UnusedImport) - - - def test_usedInSlice(self): - self.flakes('import fu; print(fu.bar[1:])') - - - def test_usedInIfBody(self): - self.flakes(''' - import fu - if True: print(fu) - - ''') - - def test_usedInIfConditional(self): - self.flakes(''' - import fu - if fu: pass - - ''') - - def test_usedInElifConditional(self): - self.flakes(''' - import fu - if False: pass - elif fu: pass - - ''') - - def test_usedInElse(self): - self.flakes(''' - import fu - if False: pass - else: print(fu) - -class Argument(Binding): - """ - Represents binding a name as an argument. - """ - - - - ''') - - def test_usedInCall(self): - self.flakes('import fu; fu.bar()') - - - def test_usedInClass(self): - self.flakes(''' - import fu - class bar: - bar = fu - - ''') - - def test_usedInClassBase(self): - self.flakes(''' - import fu - class bar(object, fu.baz): - pass - - ''') - - def test_notUsedInNestedScope(self): - self.flakes(''' - import fu - def bleh(): - pass - print(fu) - - ''') - - def test_usedInFor(self): - self.flakes(''' - import fu - for bar in range(9): - print(fu) - - ''') - - def test_usedInForElse(self): - self.flakes(''' - import fu - for bar in range(10): - pass - else: - print(fu) - - ''') - - def test_redefinedByFor(self): - self.flakes(''' - import fu - for fu in range(2): - pass - - ''', m.ImportShadowedByLoopVar) - - def test_shadowedByFor(self): - """ - Test that shadowing a global name with a for loop variable generates a - warning. - """ - self.flakes(''' - import fu - fu.bar() - for fu in (): - pass - - ''', m.ImportShadowedByLoopVar) - - def test_shadowedByForDeep(self): - """ - Test that shadowing a global name with a for loop variable nested in a - tuple unpack generates a warning. - """ - self.flakes(''' - import fu - fu.bar() - for (x, y, z, (a, b, c, (fu,))) in (): - pass - - ''', m.ImportShadowedByLoopVar) - # Same with a list instead of a tuple - self.flakes(''' - import fu - fu.bar() - for [x, y, z, (a, b, c, (fu,))] in (): - pass - ''', m.ImportShadowedByLoopVar) - - def test_usedInReturn(self): - self.flakes(''' - import fu - def fun(): - return fu - -class Assignment(Binding): - """ - Represents binding a name with an explicit assignment. - - The checker will raise warnings for any Assignment that isn't used. Also, - the checker does not consider assignments in tuple/list unpacking to be - Assignments, rather it treats them as simple Bindings. - """ - - -class NamedExprAssignment(Assignment): - """ - Represents binding a name with an assignment expression. - """ - - - - ''') - - def test_usedInOperators(self): - self.flakes('import fu; 3 + fu.bar') - self.flakes('import fu; 3 % fu.bar') - self.flakes('import fu; 3 - fu.bar') - self.flakes('import fu; 3 * fu.bar') - self.flakes('import fu; 3 ** fu.bar') - self.flakes('import fu; 3 / fu.bar') - self.flakes('import fu; 3 // fu.bar') - self.flakes('import fu; -fu.bar') - self.flakes('import fu; ~fu.bar') - self.flakes('import fu; 1 == fu.bar') - self.flakes('import fu; 1 | fu.bar') - self.flakes('import fu; 1 & fu.bar') - self.flakes('import fu; 1 ^ fu.bar') - self.flakes('import fu; 1 >> fu.bar') - self.flakes('import fu; 1 << fu.bar') - - - def test_usedInAssert(self): - self.flakes('import fu; assert fu.bar') - - - def test_usedInSubscript(self): - self.flakes('import fu; fu.bar[1]') - - - def test_usedInLogic(self): - self.flakes('import fu; fu and False') - self.flakes('import fu; fu or False') - self.flakes('import fu; not fu.bar') - - - def test_usedInList(self): - self.flakes('import fu; [fu]') - - - def test_usedInTuple(self): - self.flakes('import fu; (fu,)') - - - def test_usedInTry(self): - self.flakes(''' - import fu - try: fu - except: pass - - ''') - - def test_usedInExcept(self): - self.flakes(''' - import fu - try: fu - except: pass - - ''') - - def test_redefinedByExcept(self): - expected = [m.RedefinedWhileUnused] - # The exc variable is unused inside the exception handler. - expected.append(m.UnusedVariable) - self.flakes(''' - import fu - try: pass - except Exception as fu: pass - - ''', *expected) - - def test_usedInRaise(self): - self.flakes(''' - import fu - raise fu.bar - - ''') - - def test_usedInYield(self): - self.flakes(''' - import fu - def gen(): - yield fu - - ''') - - def test_usedInDict(self): - self.flakes('import fu; {fu:None}') - self.flakes('import fu; {1:fu}') - - - def test_usedInParameterDefault(self): - self.flakes(''' - import fu - def f(bar=fu): - pass - - ''') - - def test_usedInAttributeAssign(self): - self.flakes('import fu; fu.bar = 1') - - - def test_usedInKeywordArg(self): - self.flakes('import fu; fu.bar(stuff=fu)') - - - def test_usedInAssignment(self): - self.flakes('import fu; bar=fu') - self.flakes('import fu; n=0; n+=fu') - - - def test_usedInListComp(self): - self.flakes('import fu; [fu for _ in range(1)]') - self.flakes('import fu; [1 for _ in range(1) if fu]') - - - def test_usedInTryFinally(self): - self.flakes(''' - import fu - try: pass - finally: fu - - ''') - - self.flakes(''' - import fu - try: fu - finally: pass - ''') - - def test_usedInWhile(self): - self.flakes(''' - import fu - while 0: - fu - - ''') - - self.flakes(''' - import fu - while fu: pass - ''') - - def test_usedInGlobal(self): - """ - A 'global' statement shadowing an unused import should not prevent it - from being reported. - """ - self.flakes(''' - import fu - def f(): global fu - -class Annotation(Binding): - """ - Represents binding a name to a type without an associated value. - - As long as this name is not assigned a value in another binding, it is considered - undefined for most purposes. One notable exception is using the name as a type - annotation. - """ - - @others - - ''', m.UnusedImport) - - def test_usedAndGlobal(self): - """ - A 'global' statement shadowing a used import should not cause it to be - reported as unused. - """ - self.flakes(''' - import foo - def f(): global foo - def g(): foo.is_used() - - ''') - - def test_assignedToGlobal(self): - """ - Binding an import to a declared global should not cause it to be - reported as unused. - """ - self.flakes(''' - def f(): global foo; import foo - def g(): foo.is_used() - - ''') - - def test_usedInExec(self): - exec_stmt = 'exec("print(1)", fu.bar)' - self.flakes('import fu; %s' % exec_stmt) - - - def test_usedInLambda(self): - self.flakes('import fu; lambda: fu') - - - def test_shadowedByLambda(self): - self.flakes('import fu; lambda fu: fu', - m.UnusedImport, m.RedefinedWhileUnused) - self.flakes('import fu; lambda fu: fu\nfu()') - - - def test_usedInSliceObj(self): - self.flakes('import fu; "meow"[::fu]') - - - def test_unusedInNestedScope(self): - self.flakes(''' - def bar(): - import fu - fu - - ''', m.UnusedImport, m.UndefinedName) - - def test_methodsDontUseClassScope(self): - self.flakes(''' - class bar: - import fu - def fun(self): - fu - - ''', m.UndefinedName) - - def test_nestedFunctionsNestScope(self): - self.flakes(''' - def a(): - def b(): - fu - import fu - - ''') - - def test_nestedClassAndFunctionScope(self): - self.flakes(''' - def a(): - import fu - class b: - def c(self): - print(fu) - -class FunctionDefinition(Definition): - pass - - - - ''') - - def test_importStar(self): - """Use of import * at module level is reported.""" - self.flakes('from fu import *', m.ImportStarUsed, m.UnusedImport) - self.flakes(''' - try: - from fu import * - except: - pass - - ''', m.ImportStarUsed, m.UnusedImport) - - checker = self.flakes('from fu import *', - m.ImportStarUsed, m.UnusedImport) - - error = checker.messages[0] - assert error.message.startswith("'from %s import *' used; unable ") - assert error.message_args == ('fu', ) - - error = checker.messages[1] - assert error.message == '%r imported but unused' - assert error.message_args == ('fu.*', ) - - def test_importStar_relative(self): - """Use of import * from a relative import is reported.""" - self.flakes('from .fu import *', m.ImportStarUsed, m.UnusedImport) - self.flakes(''' - try: - from .fu import * - except: - pass - - ''', m.ImportStarUsed, m.UnusedImport) - - checker = self.flakes('from .fu import *', - m.ImportStarUsed, m.UnusedImport) - - error = checker.messages[0] - assert error.message.startswith("'from %s import *' used; unable ") - assert error.message_args == ('.fu', ) - - error = checker.messages[1] - assert error.message == '%r imported but unused' - assert error.message_args == ('.fu.*', ) - - checker = self.flakes('from .. import *', - m.ImportStarUsed, m.UnusedImport) - - error = checker.messages[0] - assert error.message.startswith("'from %s import *' used; unable ") - assert error.message_args == ('..', ) - - error = checker.messages[1] - assert error.message == '%r imported but unused' - assert error.message_args == ('from .. import *', ) - - def test_localImportStar(self): - """import * is only allowed at module level.""" - self.flakes(''' - def a(): - from fu import * - - ''', m.ImportStarNotPermitted) - self.flakes(''' - class a: - from fu import * - ''', m.ImportStarNotPermitted) - - checker = self.flakes(''' - class a: - from .. import * - ''', m.ImportStarNotPermitted) - error = checker.messages[0] - assert error.message == "'from %s import *' only allowed at module level" - assert error.message_args == ('..', ) - - def test_packageImport(self): - """ - If a dotted name is imported and used, no warning is reported. - """ - self.flakes(''' - import fu.bar - fu.bar - - ''') - - def test_unusedPackageImport(self): - """ - If a dotted name is imported and not used, an unused import warning is - reported. - """ - self.flakes('import fu.bar', m.UnusedImport) - - - def test_duplicateSubmoduleImport(self): - """ - If a submodule of a package is imported twice, an unused import warning - and a redefined while unused warning are reported. - """ - self.flakes(''' - import fu.bar, fu.bar - fu.bar - - ''', m.RedefinedWhileUnused) - self.flakes(''' - import fu.bar - import fu.bar - fu.bar - ''', m.RedefinedWhileUnused) - - def test_differentSubmoduleImport(self): - """ - If two different submodules of a package are imported, no duplicate - import warning is reported for the package. - """ - self.flakes(''' - import fu.bar, fu.baz - fu.bar, fu.baz - - ''') - self.flakes(''' - import fu.bar - import fu.baz - fu.bar, fu.baz - ''') - - def test_used_package_with_submodule_import(self): - """ - Usage of package marks submodule imports as used. - """ - self.flakes(''' - import fu - import fu.bar - fu.x - - ''') - - self.flakes(''' - import fu.bar - import fu - fu.x - ''') - - def test_used_package_with_submodule_import_of_alias(self): - """ - Usage of package by alias marks submodule imports as used. - """ - self.flakes(''' - import foo as f - import foo.bar - f.bar.do_something() - - ''') - - self.flakes(''' - import foo as f - import foo.bar.blah - f.bar.blah.do_something() - ''') - - def test_unused_package_with_submodule_import(self): - """ - When a package and its submodule are imported, only report once. - """ - checker = self.flakes(''' - import fu - import fu.bar - -class ClassDefinition(Definition): - pass - - - - ''', m.UnusedImport) - error = checker.messages[0] - assert error.message == '%r imported but unused' - assert error.message_args == ('fu.bar', ) - assert error.lineno == 5 if self.withDoctest else 3 - - def test_assignRHSFirst(self): - self.flakes('import fu; fu = fu') - self.flakes('import fu; fu, bar = fu') - self.flakes('import fu; [fu, bar] = fu') - self.flakes('import fu; fu += fu') - - - def test_tryingMultipleImports(self): - self.flakes(''' - try: - import fu - except ImportError: - import bar as fu - fu - - ''') - - def test_nonGlobalDoesNotRedefine(self): - self.flakes(''' - import fu - def a(): - fu = 3 - return fu - fu - - ''') - - def test_functionsRunLater(self): - self.flakes(''' - def a(): - fu - import fu - - ''') - - def test_functionNamesAreBoundNow(self): - self.flakes(''' - import fu - def fu(): - fu - fu - - ''', m.RedefinedWhileUnused) - - def test_ignoreNonImportRedefinitions(self): - self.flakes('a = 1; a = 2') - - - @skip("todo") - def test_importingForImportError(self): - self.flakes(''' - try: - import fu - except ImportError: - pass - - ''') - - def test_importedInClass(self): - """Imports in class scope can be used through self.""" - self.flakes(''' - class c: - import i - def __init__(self): - self.i - - ''') - - def test_importUsedInMethodDefinition(self): - """ - Method named 'foo' with default args referring to module named 'foo'. - """ - self.flakes(''' - import foo - - class Thing(object): - def foo(self, parser=foo.parse_foo): - pass - - ''') - - def test_futureImport(self): - """__future__ is special.""" - self.flakes('from __future__ import division') - self.flakes(''' - "docstring is allowed before future import" - from __future__ import division - -class ExportBinding(Binding): - """ - A binding created by an C{__all__} assignment. If the names in the list - can be determined statically, they will be treated as names for export and - additional checking applied to them. - - The only recognized C{__all__} assignment via list/tuple concatenation is in the - following format: - - __all__ = ['a'] + ['b'] + ['c'] - - Names which are imported and not otherwise used but appear in the value of - C{__all__} will not have an unused import warning reported for them. - """ - - @others - - ''') - - def test_futureImportFirst(self): - """ - __future__ imports must come before anything else. - """ - self.flakes(''' - x = 5 - from __future__ import division - - ''', m.LateFutureImport) - self.flakes(''' - from foo import bar - from __future__ import division - bar - ''', m.LateFutureImport) - - def test_futureImportUsed(self): - """__future__ is special, but names are injected in the namespace.""" - self.flakes(''' - from __future__ import division - from __future__ import print_function - - assert print_function is not division - - ''') - - def test_futureImportUndefined(self): - """Importing undefined names from __future__ fails.""" - self.flakes(''' - from __future__ import print_statement - - ''', m.FutureFeatureNotDefined) - - def test_futureImportStar(self): - """Importing '*' from __future__ fails.""" - self.flakes(''' - from __future__ import * - - ''', m.FutureFeatureNotDefined) - - -class TestSpecialAll(TestCase): - """ - Tests for suppression of unused import warnings by C{__all__}. - """ - @others - - ''', m.UnusedImport, m.UnusedVariable) - - def test_ignoredInClass(self): - """ - An C{__all__} definition in a class does not suppress unused import warnings. - """ - self.flakes(''' - import bar - class foo: - __all__ = ["bar"] - - ''', m.UnusedImport) - - def test_ignored_when_not_directly_assigned(self): - self.flakes(''' - import bar - (__all__,) = ("foo",) - - ''', m.UnusedImport) - - def test_warningSuppressed(self): - """ - If a name is imported and unused but is named in C{__all__}, no warning - is reported. - """ - self.flakes(''' - import foo - __all__ = ["foo"] - - ''') - self.flakes(''' - import foo - __all__ = ("foo",) - ''') - - def test_augmentedAssignment(self): - """ - The C{__all__} variable is defined incrementally. - """ - self.flakes(''' - import a - import c - __all__ = ['a'] - __all__ += ['b'] - if 1 < 3: - __all__ += ['c', 'd'] - - ''', m.UndefinedExport, m.UndefinedExport) - - def test_list_concatenation_assignment(self): - """ - The C{__all__} variable is defined through list concatenation. - """ - self.flakes(''' - import sys - __all__ = ['a'] + ['b'] + ['c'] - -class Scope(dict): - importStarred = False # set to True when import * is found - - def __repr__(self): - scope_cls = self.__class__.__name__ - return f'<{scope_cls} at 0x{id(self):x} {dict.__repr__(self)}>' - - - - ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport) - - def test_tuple_concatenation_assignment(self): - """ - The C{__all__} variable is defined through tuple concatenation. - """ - self.flakes(''' - import sys - __all__ = ('a',) + ('b',) + ('c',) - - ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport) - - def test_all_with_attributes(self): - self.flakes(''' - from foo import bar - __all__ = [bar.__name__] - - ''') - - def test_all_with_names(self): - # not actually valid, but shouldn't produce a crash - self.flakes(''' - from foo import bar - __all__ = [bar] - - ''') - - def test_all_with_attributes_added(self): - self.flakes(''' - from foo import bar - from bar import baz - __all__ = [bar.__name__] + [baz.__name__] - - ''') - - def test_all_mixed_attributes_and_strings(self): - self.flakes(''' - from foo import bar - from foo import baz - __all__ = ['bar', baz.__name__] - - ''') - - def test_unboundExported(self): - """ - If C{__all__} includes a name which is not bound, a warning is emitted. - """ - self.flakes(''' - __all__ = ["foo"] - - ''', m.UndefinedExport) - - # Skip this in __init__.py though, since the rules there are a little - # different. - for filename in ["foo/__init__.py", "__init__.py"]: - self.flakes(''' - __all__ = ["foo"] - ''', filename=filename) - - def test_importStarExported(self): - """ - Report undefined if import * is used - """ - self.flakes(''' - from math import * - __all__ = ['sin', 'cos'] - csc(1) - - ''', m.ImportStarUsed, m.ImportStarUsage, m.ImportStarUsage, m.ImportStarUsage) - - def test_importStarNotExported(self): - """Report unused import when not needed to satisfy __all__.""" - self.flakes(''' - from foolib import * - a = 1 - __all__ = ['a'] - - ''', m.ImportStarUsed, m.UnusedImport) - - def test_usedInGenExp(self): - """ - Using a global in a generator expression results in no warnings. - """ - self.flakes('import fu; (fu for _ in range(1))') - self.flakes('import fu; (1 for _ in range(1) if fu)') - - - def test_redefinedByGenExp(self): - """ - Re-using a global name as the loop variable for a generator - expression results in a redefinition warning. - """ - self.flakes('import fu; (1 for fu in range(1))', - m.RedefinedWhileUnused, m.UnusedImport) - - -def get_long_description(): - descr = [] - for fname in ('README.rst',): - with open(fname) as f: - descr.append(f.read()) - return '\n\n'.join(descr) - - - -class ClassScope(Scope): - pass - - - - def test_usedAsDecorator(self): - """ - Using a global name in a decorator statement results in no warnings, - but using an undefined name in a decorator statement results in an - undefined name warning. - """ - self.flakes(''' - from interior import decorate - @decorate - def f(): - return "hello" - - ''') - - self.flakes(''' - from interior import decorate - @decorate('value') - def f(): - return "hello" - ''') - - self.flakes(''' - @decorate - def f(): - return "hello" - ''', m.UndefinedName) - - def test_usedAsClassDecorator(self): - """ - Using an imported name as a class decorator results in no warnings, - but using an undefined name as a class decorator results in an - undefined name warning. - """ - self.flakes(''' - from interior import decorate - @decorate - class foo: - pass - -def test_import_basic(self): - binding = Importation('a', None, 'a') - assert binding.source_statement == 'import a' - assert str(binding) == 'a' - - -def test_import_as(self): - binding = Importation('c', None, 'a') - assert binding.source_statement == 'import a as c' - assert str(binding) == 'a as c' - - -def test_import_submodule(self): - binding = SubmoduleImportation('a.b', None) - assert binding.source_statement == 'import a.b' - assert str(binding) == 'a.b' - - -def test_import_submodule_as(self): - # A submodule import with an as clause is not a SubmoduleImportation - binding = Importation('c', None, 'a.b') - assert binding.source_statement == 'import a.b as c' - assert str(binding) == 'a.b as c' - - -def test_import_submodule_as_source_name(self): - binding = Importation('a', None, 'a.b') - assert binding.source_statement == 'import a.b as a' - assert str(binding) == 'a.b as a' - - -def test_importfrom_relative(self): - binding = ImportationFrom('a', None, '.', 'a') - assert binding.source_statement == 'from . import a' - assert str(binding) == '.a' - - -def test_importfrom_relative_parent(self): - binding = ImportationFrom('a', None, '..', 'a') - assert binding.source_statement == 'from .. import a' - assert str(binding) == '..a' - - -def test_importfrom_relative_with_module(self): - binding = ImportationFrom('b', None, '..a', 'b') - assert binding.source_statement == 'from ..a import b' - assert str(binding) == '..a.b' - - -class FunctionScope(Scope): - """ - I represent a name scope for a function. - - @ivar globals: Names declared 'global' in this function. - """ - @others - -def test_importfrom_relative_with_module_as(self): - binding = ImportationFrom('c', None, '..a', 'b') - assert binding.source_statement == 'from ..a import b as c' - assert str(binding) == '..a.b as c' - - -def test_importfrom_member(self): - binding = ImportationFrom('b', None, 'a', 'b') - assert binding.source_statement == 'from a import b' - assert str(binding) == 'a.b' - - -def test_importfrom_submodule_member(self): - binding = ImportationFrom('c', None, 'a.b', 'c') - assert binding.source_statement == 'from a.b import c' - assert str(binding) == 'a.b.c' - - -def test_importfrom_member_as(self): - binding = ImportationFrom('c', None, 'a', 'b') - assert binding.source_statement == 'from a import b as c' - assert str(binding) == 'a.b as c' - - -def test_importfrom_submodule_member_as(self): - binding = ImportationFrom('d', None, 'a.b', 'c') - assert binding.source_statement == 'from a.b import c as d' - assert str(binding) == 'a.b.c as d' - - -def test_importfrom_star(self): - binding = StarImportation('a.b', None) - assert binding.source_statement == 'from a.b import *' - assert str(binding) == 'a.b.*' - - -def test_importfrom_star_relative(self): - binding = StarImportation('.b', None) - assert binding.source_statement == 'from .b import *' - assert str(binding) == '.b.*' - - -def test_importfrom_future(self): - binding = FutureImportation('print_function', None, None) - assert binding.source_statement == 'from __future__ import print_function' - assert str(binding) == '__future__.print_function' - - -def test_unusedImport_underscore(self): - """ - The magic underscore var should be reported as unused when used as an - import alias. - """ - self.flakes('import fu as _', m.UnusedImport) - - - -def test_unusedImport(self): - self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport) - self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport) - - -class TypeScope(Scope): - pass - - - -def test_unusedImport_relative(self): - self.flakes('from . import fu', m.UnusedImport) - self.flakes('from . import fu as baz', m.UnusedImport) - self.flakes('from .. import fu', m.UnusedImport) - self.flakes('from ... import fu', m.UnusedImport) - self.flakes('from .. import fu as baz', m.UnusedImport) - self.flakes('from .bar import fu', m.UnusedImport) - self.flakes('from ..bar import fu', m.UnusedImport) - self.flakes('from ...bar import fu', m.UnusedImport) - self.flakes('from ...bar import fu as baz', m.UnusedImport) - - checker = self.flakes('from . import fu', m.UnusedImport) - - error = checker.messages[0] - assert error.message == '%r imported but unused' - assert error.message_args == ('.fu', ) - - checker = self.flakes('from . import fu as baz', m.UnusedImport) - - error = checker.messages[0] - assert error.message == '%r imported but unused' - assert error.message_args == ('.fu as baz', ) - - -def test_aliasedImport(self): - self.flakes('import fu as FU, bar as FU', - m.RedefinedWhileUnused, m.UnusedImport) - self.flakes('from moo import fu as FU, bar as FU', - m.RedefinedWhileUnused, m.UnusedImport) - - -def test_aliasedImportShadowModule(self): - """Imported aliases can shadow the source of the import.""" - self.flakes('from moo import fu as moo; moo') - self.flakes('import fu as fu; fu') - self.flakes('import fu.bar as fu; fu') - - -def test_usedImport(self): - self.flakes('import fu; print(fu)') - self.flakes('from baz import fu; print(fu)') - self.flakes('import fu; del fu') - - -def test_usedImport_relative(self): - self.flakes('from . import fu; assert fu') - self.flakes('from .bar import fu; assert fu') - self.flakes('from .. import fu; assert fu') - self.flakes('from ..bar import fu as baz; assert baz') - - -def test_redefinedWhileUnused(self): - self.flakes('import fu; fu = 3', m.RedefinedWhileUnused) - self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused) - self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused) - - -def test_redefinedIf(self): - """ - Test that importing a module twice within an if - block does raise a warning. - """ - self.flakes(''' - i = 2 - if i==1: - import os - import os - -def test_ignoredInFunction(self): - """ - An C{__all__} definition does not suppress unused import warnings in a - function scope. - """ - self.flakes(''' - def foo(): - import bar - __all__ = ["bar"] - -@path pyflakes/test -from pyflakes.messages import IsLiteral -from pyflakes.test.harness import TestCase - - -@others - ''') -@language python -@tabwidth -4 - -class Test(TestCase): - @others - -class GeneratorScope(Scope): - pass - - - - """, IsLiteral) - - def test_is_bytes(self): - self.flakes(""" - x = b'foo' - if x is b'foo': - pass - - """, IsLiteral) - - def test_is_unicode(self): - self.flakes(""" - x = u'foo' - if x is u'foo': - pass - - """, IsLiteral) - - def test_is_int(self): - self.flakes(""" - x = 10 - if x is 10: - pass - - """, IsLiteral) - - def test_is_true(self): - self.flakes(""" - x = True - if x is True: - pass - - """) - - def test_is_false(self): - self.flakes(""" - x = False - if x is False: - pass - - """) - - def test_is_not_str(self): - self.flakes(""" - x = 'foo' - if x is not 'foo': - pass - - """, IsLiteral) - - def test_is_not_bytes(self): - self.flakes(""" - x = b'foo' - if x is not b'foo': - pass - - """, IsLiteral) - - def test_is_not_unicode(self): - self.flakes(""" - x = u'foo' - if x is not u'foo': - pass - - """, IsLiteral) - - def test_is_not_int(self): - self.flakes(""" - x = 10 - if x is not 10: - pass - - """, IsLiteral) - - def test_is_not_true(self): - self.flakes(""" - x = True - if x is not True: - pass - -class ModuleScope(Scope): - """Scope for a module.""" - _futures_allowed = True - _annotations_future_enabled = False - - - - """) - - def test_is_not_false(self): - self.flakes(""" - x = False - if x is not False: - pass - - """) - - def test_left_is_str(self): - self.flakes(""" - x = 'foo' - if 'foo' is x: - pass - - """, IsLiteral) - - def test_left_is_bytes(self): - self.flakes(""" - x = b'foo' - if b'foo' is x: - pass - - """, IsLiteral) - - def test_left_is_unicode(self): - self.flakes(""" - x = u'foo' - if u'foo' is x: - pass - - """, IsLiteral) - - def test_left_is_int(self): - self.flakes(""" - x = 10 - if 10 is x: - pass - - """, IsLiteral) - - def test_left_is_true(self): - self.flakes(""" - x = True - if True is x: - pass - - """) - - def test_left_is_false(self): - self.flakes(""" - x = False - if False is x: - pass - - """) - - def test_left_is_not_str(self): - self.flakes(""" - x = 'foo' - if 'foo' is not x: - pass - - """, IsLiteral) - - def test_left_is_not_bytes(self): - self.flakes(""" - x = b'foo' - if b'foo' is not x: - pass - - """, IsLiteral) - - def test_left_is_not_unicode(self): - self.flakes(""" - x = u'foo' - if u'foo' is not x: - pass - -class DoctestScope(ModuleScope): - """Scope for a doctest.""" - - - - """, IsLiteral) - - def test_left_is_not_int(self): - self.flakes(""" - x = 10 - if 10 is not x: - pass - - """, IsLiteral) - - def test_left_is_not_true(self): - self.flakes(""" - x = True - if True is not x: - pass - - """) - - def test_left_is_not_false(self): - self.flakes(""" - x = False - if False is not x: - pass - - """) - - def test_chained_operators_is_true(self): - self.flakes(""" - x = 5 - if x is True < 4: - pass - - """) - - def test_chained_operators_is_str(self): - self.flakes(""" - x = 5 - if x is 'foo' < 4: - pass - - """, IsLiteral) - - def test_chained_operators_is_true_end(self): - self.flakes(""" - x = 5 - if 4 < x is True: - pass - - """) - - def test_chained_operators_is_str_end(self): - self.flakes(""" - x = 5 - if 4 < x is 'foo': - pass - - """, IsLiteral) - - def test_is_tuple_constant(self): - self.flakes('''\ - x = 5 - if x is (): - pass - - ''', IsLiteral) - - def test_is_tuple_constant_containing_constants(self): - self.flakes('''\ - x = 5 - if x is (1, '2', True, (1.5, ())): - pass - - ''', IsLiteral) - - def test_is_tuple_containing_variables_ok(self): - # a bit nonsensical, but does not trigger a SyntaxWarning - self.flakes('''\ - x = 5 - if x is (x,): - pass - -class DetectClassScopedMagic: - names = dir() - - - -def test_is_str(self): - self.flakes(""" - x = 'foo' - if x is 'foo': - pass - -@path pyflakes/test -from sys import version_info - -from pyflakes.test.harness import TestCase, skipIf - - -@skipIf(version_info < (3, 10), "Python >= 3.10 only") -@others - ''') -@language python -@tabwidth -4 - -class TestMatch(TestCase): - @others - - ''') - self.flakes(''' - def f(): - x = [1, 2, 3] - match x: - case [1, y, 3]: - print(f'matched {y}') - ''') - self.flakes(''' - def f(): - x = {'foo': 1} - match x: - case {'foo': y}: - print(f'matched {y}') - ''') - - def test_match_pattern_matched_class(self): - self.flakes(''' - from a import B - - match 1: - case B(x=1) as y: - print(f'matched {y}') - - ''') - self.flakes(''' - from a import B - - match 1: - case B(a, x=z) as y: - print(f'matched {y} {a} {z}') - ''') - - def test_match_placeholder(self): - self.flakes(''' - def f(): - match 1: - case _: - print('catchall!') - - ''') - - def test_match_singleton(self): - self.flakes(''' - match 1: - case True: - print('true') - - ''') - - def test_match_or_pattern(self): - self.flakes(''' - match 1: - case 1 | 2: - print('one or two') - - ''') - - def test_match_star(self): - self.flakes(''' - x = [1, 2, 3] - match x: - case [1, *y]: - print(f'captured: {y}') - - ''') - - def test_match_double_star(self): - self.flakes(''' - x = {'foo': 'bar', 'baz': 'womp'} - match x: - case {'foo': k1, **rest}: - print(f'{k1=} {rest=}') - - ''') - - def test_defined_in_different_branches(self): - self.flakes(''' - def f(x): - match x: - case 1: - def y(): pass - case _: - def y(): print(1) - return y - -def getNodeName(node): - # Returns node.id, or node.name, or None - if hasattr(node, 'id'): # One of the many nodes with an id - return node.id - if hasattr(node, 'name'): # an ExceptHandler node - return node.name - if hasattr(node, 'rest'): # a MatchMapping node - return node.rest - - - -def test_match_bindings(self): - self.flakes(''' - def f(): - x = 1 - match x: - case 1 as y: - print(f'matched as {y}') - -@path pyflakes/test -""" -Tests for various Pyflakes behavior. -""" - -from sys import version_info - -from pyflakes import messages as m -from pyflakes.test.harness import TestCase, skip, skipIf - - -@others - ''') -@language python -@tabwidth -4 - -class Test(TestCase): - - @others - - ''', m.UndefinedLocal, m.UnusedVariable) - - def test_redefinedInGenerator(self): - """ - Test that reusing a variable in a generator does not raise - a warning. - """ - self.flakes(''' - a = 1 - (1 for a, b in [(1, 2)]) - - ''') - self.flakes(''' - class A: - a = 1 - list(1 for a, b in [(1, 2)]) - ''') - self.flakes(''' - def f(): - a = 1 - (1 for a, b in [(1, 2)]) - ''', m.UnusedVariable) - self.flakes(''' - (1 for a, b in [(1, 2)]) - (1 for a, b in [(1, 2)]) - ''') - self.flakes(''' - for a, b in [(1, 2)]: - pass - (1 for a, b in [(1, 2)]) - ''') - - def test_redefinedInSetComprehension(self): - """ - Test that reusing a variable in a set comprehension does not raise - a warning. - """ - self.flakes(''' - a = 1 - {1 for a, b in [(1, 2)]} - - ''') - self.flakes(''' - class A: - a = 1 - {1 for a, b in [(1, 2)]} - ''') - self.flakes(''' - def f(): - a = 1 - {1 for a, b in [(1, 2)]} - ''', m.UnusedVariable) - self.flakes(''' - {1 for a, b in [(1, 2)]} - {1 for a, b in [(1, 2)]} - ''') - self.flakes(''' - for a, b in [(1, 2)]: - pass - {1 for a, b in [(1, 2)]} - ''') - - def test_redefinedInDictComprehension(self): - """ - Test that reusing a variable in a dict comprehension does not raise - a warning. - """ - self.flakes(''' - a = 1 - {1: 42 for a, b in [(1, 2)]} - - ''') - self.flakes(''' - class A: - a = 1 - {1: 42 for a, b in [(1, 2)]} - ''') - self.flakes(''' - def f(): - a = 1 - {1: 42 for a, b in [(1, 2)]} - ''', m.UnusedVariable) - self.flakes(''' - {1: 42 for a, b in [(1, 2)]} - {1: 42 for a, b in [(1, 2)]} - ''') - self.flakes(''' - for a, b in [(1, 2)]: - pass - {1: 42 for a, b in [(1, 2)]} - ''') - - def test_redefinedFunction(self): - """ - Test that shadowing a function definition with another one raises a - warning. - """ - self.flakes(''' - def a(): pass - def a(): pass - - ''', m.RedefinedWhileUnused) - - def test_redefined_function_shadows_variable(self): - self.flakes(''' - x = 1 - def x(): pass - - ''', m.RedefinedWhileUnused) - - def test_redefinedUnderscoreFunction(self): - """ - Test that shadowing a function definition named with underscore doesn't - raise anything. - """ - self.flakes(''' - def _(): pass - def _(): pass - - ''') - - def test_redefinedUnderscoreImportation(self): - """ - Test that shadowing an underscore importation raises a warning. - """ - self.flakes(''' - from .i18n import _ - def _(): pass - -def _is_typing_helper(node, is_name_match_fn, scope_stack): - """ - Internal helper to determine whether or not something is a member of a - typing module. This is used as part of working out whether we are within a - type annotation context. - - Note: you probably don't want to use this function directly. Instead see the - utils below which wrap it (`_is_typing` and `_is_any_typing_member`). - """ - - def _bare_name_is_attr(name): - for scope in reversed(scope_stack): - if name in scope: - return ( - isinstance(scope[name], ImportationFrom) and - scope[name].module in TYPING_MODULES and - is_name_match_fn(scope[name].real_name) - ) - - return False - - def _module_scope_is_typing(name): - for scope in reversed(scope_stack): - if name in scope: - return ( - isinstance(scope[name], Importation) and - scope[name].fullName in TYPING_MODULES - ) - - return False - - return ( - ( - isinstance(node, ast.Name) and - _bare_name_is_attr(node.id) - ) or ( - isinstance(node, ast.Attribute) and - isinstance(node.value, ast.Name) and - _module_scope_is_typing(node.value.id) and - is_name_match_fn(node.attr) - ) - ) - - - - ''', m.RedefinedWhileUnused) - - def test_redefinedClassFunction(self): - """ - Test that shadowing a function definition in a class suite with another - one raises a warning. - """ - self.flakes(''' - class A: - def a(): pass - def a(): pass - - ''', m.RedefinedWhileUnused) - - def test_redefinedIfElseFunction(self): - """ - Test that shadowing a function definition twice in an if - and else block does not raise a warning. - """ - self.flakes(''' - if True: - def a(): pass - else: - def a(): pass - - ''') - - def test_redefinedIfFunction(self): - """ - Test that shadowing a function definition within an if block - raises a warning. - """ - self.flakes(''' - if True: - def a(): pass - def a(): pass - - ''', m.RedefinedWhileUnused) - - def test_redefinedTryExceptFunction(self): - """ - Test that shadowing a function definition twice in try - and except block does not raise a warning. - """ - self.flakes(''' - try: - def a(): pass - except: - def a(): pass - - ''') - - def test_redefinedTryFunction(self): - """ - Test that shadowing a function definition within a try block - raises a warning. - """ - self.flakes(''' - try: - def a(): pass - def a(): pass - except: - pass - - ''', m.RedefinedWhileUnused) - - def test_redefinedIfElseInListComp(self): - """ - Test that shadowing a variable in a list comprehension in - an if and else block does not raise a warning. - """ - self.flakes(''' - if False: - a = 1 - else: - [a for a in '12'] - - ''') - - def test_functionDecorator(self): - """ - Test that shadowing a function definition with a decorated version of - that function does not raise a warning. - """ - self.flakes(''' - from somewhere import somedecorator - - def a(): pass - a = somedecorator(a) - - ''') - - def test_classFunctionDecorator(self): - """ - Test that shadowing a function definition in a class suite with a - decorated version of that function does not raise a warning. - """ - self.flakes(''' - class A: - def a(): pass - a = classmethod(a) - - ''') - - def test_modernProperty(self): - self.flakes(""" - class A: - @property - def t(self): - pass - @t.setter - def t(self, value): - pass - @t.deleter - def t(self): - pass - - """) - - def test_unaryPlus(self): - """Don't die on unary +.""" - self.flakes('+1') - - -def _is_typing(node, typing_attr, scope_stack): - """ - Determine whether `node` represents the member of a typing module specified - by `typing_attr`. - - This is used as part of working out whether we are within a type annotation - context. - """ - return _is_typing_helper(node, lambda x: x == typing_attr, scope_stack) - - - - def test_undefinedBaseClass(self): - """ - If a name in the base list of a class definition is undefined, a - warning is emitted. - """ - self.flakes(''' - class foo(foo): - pass - - ''', m.UndefinedName) - - def test_classNameUndefinedInClassBody(self): - """ - If a class name is used in the body of that class's definition and - the name is not already defined, a warning is emitted. - """ - self.flakes(''' - class foo: - foo - - ''', m.UndefinedName) - - def test_classNameDefinedPreviously(self): - """ - If a class name is used in the body of that class's definition and - the name was previously defined in some other way, no warning is - emitted. - """ - self.flakes(''' - foo = None - class foo: - foo - - ''') - - def test_classRedefinition(self): - """ - If a class is defined twice in the same module, a warning is emitted. - """ - self.flakes(''' - class Foo: - pass - class Foo: - pass - - ''', m.RedefinedWhileUnused) - - def test_functionRedefinedAsClass(self): - """ - If a function is redefined as a class, a warning is emitted. - """ - self.flakes(''' - def Foo(): - pass - class Foo: - pass - - ''', m.RedefinedWhileUnused) - - def test_classRedefinedAsFunction(self): - """ - If a class is redefined as a function, a warning is emitted. - """ - self.flakes(''' - class Foo: - pass - def Foo(): - pass - - ''', m.RedefinedWhileUnused) - - def test_classWithReturn(self): - """ - If a return is used inside a class, a warning is emitted. - """ - self.flakes(''' - class Foo(object): - return - - ''', m.ReturnOutsideFunction) - - def test_moduleWithReturn(self): - """ - If a return is used at the module level, a warning is emitted. - """ - self.flakes(''' - return - - ''', m.ReturnOutsideFunction) - - def test_classWithYield(self): - """ - If a yield is used inside a class, a warning is emitted. - """ - self.flakes(''' - class Foo(object): - yield - - ''', m.YieldOutsideFunction) - - def test_moduleWithYield(self): - """ - If a yield is used at the module level, a warning is emitted. - """ - self.flakes(''' - yield - -def _is_any_typing_member(node, scope_stack): - """ - Determine whether `node` represents any member of a typing module. - - This is used as part of working out whether we are within a type annotation - context. - """ - return _is_typing_helper(node, lambda x: True, scope_stack) - - - - ''', m.YieldOutsideFunction) - - def test_classWithYieldFrom(self): - """ - If a yield from is used inside a class, a warning is emitted. - """ - self.flakes(''' - class Foo(object): - yield from range(10) - - ''', m.YieldOutsideFunction) - - def test_moduleWithYieldFrom(self): - """ - If a yield from is used at the module level, a warning is emitted. - """ - self.flakes(''' - yield from range(10) - - ''', m.YieldOutsideFunction) - - def test_continueOutsideLoop(self): - self.flakes(''' - continue - - ''', m.ContinueOutsideLoop) - - self.flakes(''' - def f(): - continue - ''', m.ContinueOutsideLoop) - - self.flakes(''' - while True: - pass - else: - continue - ''', m.ContinueOutsideLoop) - - self.flakes(''' - while True: - pass - else: - if 1: - if 2: - continue - ''', m.ContinueOutsideLoop) - - self.flakes(''' - while True: - def f(): - continue - ''', m.ContinueOutsideLoop) - - self.flakes(''' - while True: - class A: - continue - ''', m.ContinueOutsideLoop) - - def test_continueInsideLoop(self): - self.flakes(''' - while True: - continue - - ''') - - self.flakes(''' - for i in range(10): - continue - ''') - - self.flakes(''' - while True: - if 1: - continue - ''') - - self.flakes(''' - for i in range(10): - if 1: - continue - ''') - - self.flakes(''' - while True: - while True: - pass - else: - continue - else: - pass - ''') - - self.flakes(''' - while True: - try: - pass - finally: - while True: - continue - ''') - - def test_breakOutsideLoop(self): - self.flakes(''' - break - - ''', m.BreakOutsideLoop) - - self.flakes(''' - def f(): - break - ''', m.BreakOutsideLoop) - - self.flakes(''' - while True: - pass - else: - break - ''', m.BreakOutsideLoop) - - self.flakes(''' - while True: - pass - else: - if 1: - if 2: - break - ''', m.BreakOutsideLoop) - - self.flakes(''' - while True: - def f(): - break - ''', m.BreakOutsideLoop) - - self.flakes(''' - while True: - class A: - break - ''', m.BreakOutsideLoop) - - self.flakes(''' - try: - pass - finally: - break - ''', m.BreakOutsideLoop) - - def test_breakInsideLoop(self): - self.flakes(''' - while True: - break - - ''') - - self.flakes(''' - for i in range(10): - break - ''') - - self.flakes(''' - while True: - if 1: - break - ''') - - self.flakes(''' - for i in range(10): - if 1: - break - ''') - - self.flakes(''' - while True: - while True: - pass - else: - break - else: - pass - ''') - - self.flakes(''' - while True: - try: - pass - finally: - while True: - break - ''') - - self.flakes(''' - while True: - try: - pass - finally: - break - ''') - - self.flakes(''' - while True: - try: - pass - finally: - if 1: - if 2: - break - ''') - - def test_defaultExceptLast(self): - """ - A default except block should be last. - - YES: - - try: - ... - except Exception: - ... - except: - ... - - NO: - - try: - ... - except: - ... - except Exception: - ... - """ - self.flakes(''' - try: - pass - except ValueError: - pass - - ''') - - self.flakes(''' - try: - pass - except ValueError: - pass - except: - pass - ''') - - self.flakes(''' - try: - pass - except: - pass - ''') - - self.flakes(''' - try: - pass - except ValueError: - pass - else: - pass - ''') - - self.flakes(''' - try: - pass - except: - pass - else: - pass - ''') - - self.flakes(''' - try: - pass - except ValueError: - pass - except: - pass - else: - pass - ''') - - def test_defaultExceptNotLast(self): - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - except: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - except: - pass - except ValueError: - pass - ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - else: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except: - pass - else: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - except: - pass - else: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - except: - pass - except ValueError: - pass - else: - pass - ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - finally: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except: - pass - finally: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - except: - pass - finally: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - except: - pass - except ValueError: - pass - finally: - pass - ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - else: - pass - finally: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except: - pass - else: - pass - finally: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - except: - pass - else: - pass - finally: - pass - ''', m.DefaultExceptNotLast) - - self.flakes(''' - try: - pass - except: - pass - except ValueError: - pass - except: - pass - except ValueError: - pass - else: - pass - finally: - pass - ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) - - def test_starredAssignmentNoError(self): - """ - Python 3 extended iterable unpacking - """ - self.flakes(''' - a, *b = range(10) - - ''') - - self.flakes(''' - *a, b = range(10) - ''') - - self.flakes(''' - a, *b, c = range(10) - ''') - - self.flakes(''' - (a, *b) = range(10) - ''') - - self.flakes(''' - (*a, b) = range(10) - ''') - - self.flakes(''' - (a, *b, c) = range(10) - ''') - - self.flakes(''' - [a, *b] = range(10) - ''') - - self.flakes(''' - [*a, b] = range(10) - ''') - - self.flakes(''' - [a, *b, c] = range(10) - ''') - - # Taken from test_unpack_ex.py in the cPython source - s = ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \ - ", *rest = range(1<<8)" - self.flakes(s) - - s = "(" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \ - ", *rest) = range(1<<8)" - self.flakes(s) - - s = "[" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \ - ", *rest] = range(1<<8)" - self.flakes(s) - - def test_starredAssignmentErrors(self): - """ - SyntaxErrors (not encoded in the ast) surrounding Python 3 extended - iterable unpacking - """ - # Taken from test_unpack_ex.py in the cPython source - s = ", ".join("a%d" % i for i in range(1 << 8)) + \ - ", *rest = range(1<<8 + 1)" - self.flakes(s, m.TooManyExpressionsInStarredAssignment) - - s = "(" + ", ".join("a%d" % i for i in range(1 << 8)) + \ - ", *rest) = range(1<<8 + 1)" - self.flakes(s, m.TooManyExpressionsInStarredAssignment) - - s = "[" + ", ".join("a%d" % i for i in range(1 << 8)) + \ - ", *rest] = range(1<<8 + 1)" - self.flakes(s, m.TooManyExpressionsInStarredAssignment) - - s = ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \ - ", *rest = range(1<<8 + 2)" - self.flakes(s, m.TooManyExpressionsInStarredAssignment) - - s = "(" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \ - ", *rest) = range(1<<8 + 2)" - self.flakes(s, m.TooManyExpressionsInStarredAssignment) - - s = "[" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \ - ", *rest] = range(1<<8 + 2)" - self.flakes(s, m.TooManyExpressionsInStarredAssignment) - - # No way we can actually test this! - # s = "*rest, " + ", ".join("a%d" % i for i in range(1<<24)) + \ - # ", *rest = range(1<<24 + 1)" - # self.flakes(s, m.TooManyExpressionsInStarredAssignment) - - self.flakes(''' - a, *b, *c = range(10) - -def is_typing_overload(value, scope_stack): - return ( - isinstance(value.source, (ast.FunctionDef, ast.AsyncFunctionDef)) and - any( - _is_typing(dec, 'overload', scope_stack) - for dec in value.source.decorator_list - ) - ) - - - - ''', m.TwoStarredExpressions) - - self.flakes(''' - a, *b, c, *d = range(10) - ''', m.TwoStarredExpressions) - - self.flakes(''' - *a, *b, *c = range(10) - ''', m.TwoStarredExpressions) - - self.flakes(''' - (a, *b, *c) = range(10) - ''', m.TwoStarredExpressions) - - self.flakes(''' - (a, *b, c, *d) = range(10) - ''', m.TwoStarredExpressions) - - self.flakes(''' - (*a, *b, *c) = range(10) - ''', m.TwoStarredExpressions) - - self.flakes(''' - [a, *b, *c] = range(10) - ''', m.TwoStarredExpressions) - - self.flakes(''' - [a, *b, c, *d] = range(10) - ''', m.TwoStarredExpressions) - - self.flakes(''' - [*a, *b, *c] = range(10) - ''', m.TwoStarredExpressions) - - @skip("todo: Too hard to make this warn but other cases stay silent") - def test_doubleAssignment(self): - """ - If a variable is re-assigned to without being used, no warning is - emitted. - """ - self.flakes(''' - x = 10 - x = 20 - - ''', m.RedefinedWhileUnused) - - def test_doubleAssignmentConditionally(self): - """ - If a variable is re-assigned within a conditional, no warning is - emitted. - """ - self.flakes(''' - x = 10 - if True: - x = 20 - - ''') - - def test_doubleAssignmentWithUse(self): - """ - If a variable is re-assigned to after being used, no warning is - emitted. - """ - self.flakes(''' - x = 10 - y = x * 2 - x = 20 - - ''') - - def test_comparison(self): - """ - If a defined name is used on either side of any of the six comparison - operators, no warning is emitted. - """ - self.flakes(''' - x = 10 - y = 20 - x < y - x <= y - x == y - x != y - x >= y - x > y - - ''') - - def test_identity(self): - """ - If a defined name is used on either side of an identity test, no - warning is emitted. - """ - self.flakes(''' - x = 10 - y = 20 - x is y - x is not y - - ''') - - def test_containment(self): - """ - If a defined name is used on either side of a containment test, no - warning is emitted. - """ - self.flakes(''' - x = 10 - y = 20 - x in y - x not in y - - ''') - - def test_loopControl(self): - """ - break and continue statements are supported. - """ - self.flakes(''' - for x in [1, 2]: - break - - ''') - self.flakes(''' - for x in [1, 2]: - continue - ''') - - def test_ellipsis(self): - """ - Ellipsis in a slice is supported. - """ - self.flakes(''' - [1, 2][...] - - ''') - - def test_extendedSlice(self): - """ - Extended slices are supported. - """ - self.flakes(''' - x = 3 - [1, 2][x,:] - - ''') - - def test_varAugmentedAssignment(self): - """ - Augmented assignment of a variable is supported. - We don't care about var refs. - """ - self.flakes(''' - foo = 0 - foo += 1 - -class AnnotationState: - NONE = 0 - STRING = 1 - BARE = 2 - - - - ''') - - def test_attrAugmentedAssignment(self): - """ - Augmented assignment of attributes is supported. - We don't care about attr refs. - """ - self.flakes(''' - foo = None - foo.bar += foo.baz - - ''') - - def test_globalDeclaredInDifferentScope(self): - """ - A 'global' can be declared in one scope and reused in another. - """ - self.flakes(''' - def f(): global foo - def g(): foo = 'anything'; foo.is_used() - - ''') - - def test_function_arguments(self): - """ - Test to traverse ARG and ARGUMENT handler - """ - self.flakes(''' - def foo(a, b): - pass - - ''') - - self.flakes(''' - def foo(a, b, c=0): - pass - ''') - - self.flakes(''' - def foo(a, b, c=0, *args): - pass - ''') - - self.flakes(''' - def foo(a, b, c=0, *args, **kwargs): - pass - ''') - - def test_function_arguments_python3(self): - self.flakes(''' - def foo(a, b, c=0, *args, d=0, **kwargs): - pass - - ''') - - -class TestUnusedAssignment(TestCase): - """ - Tests for warning about unused assignments. - """ - - @others - - ''', m.UnusedVariable) - - def test_unusedUnderscoreVariable(self): - """ - Don't warn when the magic "_" (underscore) variable is unused. - See issue #202. - """ - self.flakes(''' - def a(unused_param): - _ = unused_param - - ''') - - def test_unusedVariableAsLocals(self): - """ - Using locals() it is perfectly valid to have unused variables - """ - self.flakes(''' - def a(): - b = 1 - return locals() - - ''') - - def test_unusedVariableNoLocals(self): - """ - Using locals() in wrong scope should not matter - """ - self.flakes(''' - def a(): - locals() - def a(): - b = 1 - return - - ''', m.UnusedVariable) - - @skip("todo: Difficult because it doesn't apply in the context of a loop") - def test_unusedReassignedVariable(self): - """ - Shadowing a used variable can still raise an UnusedVariable warning. - """ - self.flakes(''' - def a(): - b = 1 - b.foo() - b = 2 - - ''', m.UnusedVariable) - - def test_variableUsedInLoop(self): - """ - Shadowing a used variable cannot raise an UnusedVariable warning in the - context of a loop. - """ - self.flakes(''' - def a(): - b = True - while b: - b = False - -def in_annotation(func): - @functools.wraps(func) - def in_annotation_func(self, *args, **kwargs): - with self._enter_annotation(): - return func(self, *args, **kwargs) - return in_annotation_func - - - - ''') - - def test_assignToGlobal(self): - """ - Assigning to a global and then not using that global is perfectly - acceptable. Do not mistake it for an unused local variable. - """ - self.flakes(''' - b = 0 - def a(): - global b - b = 1 - - ''') - - def test_assignToNonlocal(self): - """ - Assigning to a nonlocal and then not using that binding is perfectly - acceptable. Do not mistake it for an unused local variable. - """ - self.flakes(''' - b = b'0' - def a(): - nonlocal b - b = b'1' - - ''') - - def test_assignToMember(self): - """ - Assigning to a member of another object and then not using that member - variable is perfectly acceptable. Do not mistake it for an unused - local variable. - """ - # XXX: Adding this test didn't generate a failure. Maybe not - # necessary? - self.flakes(''' - class b: - pass - def a(): - b.foo = 1 - - ''') - - def test_assignInForLoop(self): - """ - Don't warn when a variable in a for loop is assigned to but not used. - """ - self.flakes(''' - def f(): - for i in range(10): - pass - - ''') - - def test_assignInListComprehension(self): - """ - Don't warn when a variable in a list comprehension is - assigned to but not used. - """ - self.flakes(''' - def f(): - [None for i in range(10)] - - ''') - - def test_generatorExpression(self): - """ - Don't warn when a variable in a generator expression is - assigned to but not used. - """ - self.flakes(''' - def f(): - (None for i in range(10)) - - ''') - - def test_assignmentInsideLoop(self): - """ - Don't warn when a variable assignment occurs lexically after its use. - """ - self.flakes(''' - def f(): - x = None - for i in range(10): - if i > 2: - return x - x = i * 2 - - ''') - - def test_tupleUnpacking(self): - """ - Don't warn when a variable included in tuple unpacking is unused. It's - very common for variables in a tuple unpacking assignment to be unused - in good Python code, so warning will only create false positives. - """ - self.flakes(''' - def f(tup): - (x, y) = tup - - ''') - self.flakes(''' - def f(): - (x, y) = 1, 2 - ''', m.UnusedVariable, m.UnusedVariable) - self.flakes(''' - def f(): - (x, y) = coords = 1, 2 - if x > 1: - print(coords) - ''') - self.flakes(''' - def f(): - (x, y) = coords = 1, 2 - ''', m.UnusedVariable) - self.flakes(''' - def f(): - coords = (x, y) = 1, 2 - ''', m.UnusedVariable) - - def test_listUnpacking(self): - """ - Don't warn when a variable included in list unpacking is unused. - """ - self.flakes(''' - def f(tup): - [x, y] = tup - - ''') - self.flakes(''' - def f(): - [x, y] = [1, 2] - ''', m.UnusedVariable, m.UnusedVariable) - - def test_closedOver(self): - """ - Don't warn when the assignment is used in an inner function. - """ - self.flakes(''' - def barMaker(): - foo = 5 - def bar(): - return foo - return bar - -def in_string_annotation(func): - @functools.wraps(func) - def in_annotation_func(self, *args, **kwargs): - with self._enter_annotation(AnnotationState.STRING): - return func(self, *args, **kwargs) - return in_annotation_func - - - - ''') - - def test_doubleClosedOver(self): - """ - Don't warn when the assignment is used in an inner function, even if - that inner function itself is in an inner function. - """ - self.flakes(''' - def barMaker(): - foo = 5 - def bar(): - def baz(): - return foo - return bar - - ''') - - def test_tracebackhideSpecialVariable(self): - """ - Do not warn about unused local variable __tracebackhide__, which is - a special variable for py.test. - """ - self.flakes(""" - def helper(): - __tracebackhide__ = True - - """) - - def test_debuggerskipSpecialVariable(self): - """ - Do not warn about unused local variable __debuggerskip__, which is - a special variable for IPython. - """ - self.flakes(""" - def helper(): - __debuggerskip__ = True - - """) - - def test_ifexp(self): - """ - Test C{foo if bar else baz} statements. - """ - self.flakes("a = 'moo' if True else 'oink'") - self.flakes("a = foo if True else 'oink'", m.UndefinedName) - self.flakes("a = 'moo' if True else bar", m.UndefinedName) - - - def test_if_tuple(self): - """ - Test C{if (foo,)} conditions. - """ - self.flakes("""if (): pass""") - self.flakes(""" - if ( - True - ): - pass - - """) - self.flakes(""" - if ( - True, - ): - pass - """, m.IfTuple) - self.flakes(""" - x = 1 if ( - True, - ) else 2 - """, m.IfTuple) - - def test_withStatementNoNames(self): - """ - No warnings are emitted for using inside or after a nameless C{with} - statement a name defined beforehand. - """ - self.flakes(''' - bar = None - with open("foo"): - bar - bar - - ''') - - def test_withStatementSingleName(self): - """ - No warnings are emitted for using a name defined by a C{with} statement - within the suite or afterwards. - """ - self.flakes(''' - with open('foo') as bar: - bar - bar - - ''') - - def test_withStatementAttributeName(self): - """ - No warnings are emitted for using an attribute as the target of a - C{with} statement. - """ - self.flakes(''' - import foo - with open('foo') as foo.bar: - pass - - ''') - - def test_withStatementSubscript(self): - """ - No warnings are emitted for using a subscript as the target of a - C{with} statement. - """ - self.flakes(''' - import foo - with open('foo') as foo[0]: - pass - - ''') - - def test_withStatementSubscriptUndefined(self): - """ - An undefined name warning is emitted if the subscript used as the - target of a C{with} statement is not defined. - """ - self.flakes(''' - import foo - with open('foo') as foo[bar]: - pass - -class Checker: - """I check the cleanliness and sanity of Python code.""" - - << Checker: class data >> - - @others - - ''', m.UndefinedName) - - def test_withStatementTupleNames(self): - """ - No warnings are emitted for using any of the tuple of names defined by - a C{with} statement within the suite or afterwards. - """ - self.flakes(''' - with open('foo') as (bar, baz): - bar, baz - bar, baz - - ''') - - def test_withStatementListNames(self): - """ - No warnings are emitted for using any of the list of names defined by a - C{with} statement within the suite or afterwards. - """ - self.flakes(''' - with open('foo') as [bar, baz]: - bar, baz - bar, baz - - ''') - - def test_withStatementComplicatedTarget(self): - """ - If the target of a C{with} statement uses any or all of the valid forms - for that part of the grammar (See - U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}), - the names involved are checked both for definedness and any bindings - created are respected in the suite of the statement and afterwards. - """ - self.flakes(''' - c = d = e = g = h = i = None - with open('foo') as [(a, b), c[d], e.f, g[h:i]]: - a, b, c, d, e, g, h, i - a, b, c, d, e, g, h, i - - ''') - - def test_withStatementSingleNameUndefined(self): - """ - An undefined name warning is emitted if the name first defined by a - C{with} statement is used before the C{with} statement. - """ - self.flakes(''' - bar - with open('foo') as bar: - pass - - ''', m.UndefinedName) - - def test_withStatementTupleNamesUndefined(self): - """ - An undefined name warning is emitted if a name first defined by the - tuple-unpacking form of the C{with} statement is used before the - C{with} statement. - """ - self.flakes(''' - baz - with open('foo') as (bar, baz): - pass - - ''', m.UndefinedName) - - def test_withStatementSingleNameRedefined(self): - """ - A redefined name warning is emitted if a name bound by an import is - rebound by the name defined by a C{with} statement. - """ - self.flakes(''' - import bar - with open('foo') as bar: - pass - - ''', m.RedefinedWhileUnused) - - def test_withStatementTupleNamesRedefined(self): - """ - A redefined name warning is emitted if a name bound by an import is - rebound by one of the names defined by the tuple-unpacking form of a - C{with} statement. - """ - self.flakes(''' - import bar - with open('foo') as (bar, baz): - pass - - ''', m.RedefinedWhileUnused) - - def test_withStatementUndefinedInside(self): - """ - An undefined name warning is emitted if a name is used inside the - body of a C{with} statement without first being bound. - """ - self.flakes(''' - with open('foo') as bar: - baz - - ''', m.UndefinedName) - - def test_withStatementNameDefinedInBody(self): - """ - A name defined in the body of a C{with} statement can be used after - the body ends without warning. - """ - self.flakes(''' - with open('foo') as bar: - baz = 10 - baz - - ''') - - def test_withStatementUndefinedInExpression(self): - """ - An undefined name warning is emitted if a name in the I{test} - expression of a C{with} statement is undefined. - """ - self.flakes(''' - with bar as baz: - pass - - ''', m.UndefinedName) - - self.flakes(''' - with bar as bar: - pass - ''', m.UndefinedName) - - def test_dictComprehension(self): - """ - Dict comprehensions are properly handled. - """ - self.flakes(''' - a = {1: x for x in range(10)} - - ''') - - def test_setComprehensionAndLiteral(self): - """ - Set comprehensions are properly handled. - """ - self.flakes(''' - a = {1, 2, 3} - b = {x for x in range(10)} - - ''') - - def test_exceptionUsedInExcept(self): - self.flakes(''' - try: pass - except Exception as e: e - - ''') - - self.flakes(''' - def download_review(): - try: pass - except Exception as e: e - ''') - - def test_exceptionUnusedInExcept(self): - self.flakes(''' - try: pass - except Exception as e: pass - - ''', m.UnusedVariable) - - @skipIf(version_info < (3, 11), 'new in Python 3.11') - def test_exception_unused_in_except_star(self): - self.flakes(''' - try: - pass - except* OSError as e: - pass - - ''', m.UnusedVariable) - - def test_exceptionUnusedInExceptInFunction(self): - self.flakes(''' - def download_review(): - try: pass - except Exception as e: pass - - ''', m.UnusedVariable) - - def test_exceptWithoutNameInFunction(self): - """ - Don't issue false warning when an unnamed exception is used. - Previously, there would be a false warning, but only when the - try..except was in a function - """ - self.flakes(''' - import tokenize - def foo(): - try: pass - except tokenize.TokenError: pass - - ''') - - def test_exceptWithoutNameInFunctionTuple(self): - """ - Don't issue false warning when an unnamed exception is used. - This example catches a tuple of exception types. - """ - self.flakes(''' - import tokenize - def foo(): - try: pass - except (tokenize.TokenError, IndentationError): pass - - ''') - - def test_augmentedAssignmentImportedFunctionCall(self): - """ - Consider a function that is called on the right part of an - augassign operation to be used. - """ - self.flakes(''' - from foo import bar - baz = 0 - baz += bar() - - ''') - - def test_assert_without_message(self): - """An assert without a message is not an error.""" - self.flakes(''' - a = 1 - assert a - - ''') - - def test_assert_with_message(self): - """An assert with a message is not an error.""" - self.flakes(''' - a = 1 - assert a, 'x' - - ''') - - def test_assert_tuple(self): - """An assert of a non-empty tuple is always True.""" - self.flakes(''' - assert (False, 'x') - assert (False, ) - - ''', m.AssertTuple, m.AssertTuple) - - def test_assert_tuple_empty(self): - """An assert of an empty tuple is always False.""" - self.flakes(''' - assert () - - ''') - - def test_assert_static(self): - """An assert of a static value is not an error.""" - self.flakes(''' - assert True - assert 1 - - ''') - - def test_yieldFromUndefined(self): - """ - Test C{yield from} statement - """ - self.flakes(''' - def bar(): - yield from foo() - - ''', m.UndefinedName) - - def test_f_string(self): - """Test PEP 498 f-strings are treated as a usage.""" - self.flakes(''' - baz = 0 - print(f'\x7b4*baz\N{RIGHT CURLY BRACKET}') - - ''') - - def test_assign_expr(self): - """Test PEP 572 assignment expressions are treated as usage / write.""" - self.flakes(''' - from foo import y - print(x := y) - print(x) - - ''') - - def test_assign_expr_generator_scope(self): - """Test assignment expressions in generator expressions.""" - self.flakes(''' - if (any((y := x[0]) for x in [[True]])): - print(y) - - ''') - - def test_assign_expr_generator_scope_reassigns_parameter(self): - self.flakes(''' - def foo(x): - fns = [lambda x: x + 1, lambda x: x + 2, lambda x: x + 3] - return [(x := fn(x)) for fn in fns] - - ''') - - def test_assign_expr_nested(self): - """Test assignment expressions in nested expressions.""" - self.flakes(''' - if ([(y:=x) for x in range(4) if [(z:=q) for q in range(4)]]): - print(y) - print(z) - - ''') - - -class TestStringFormatting(TestCase): - - @others - - ''', m.FStringMissingPlaceholders) - self.flakes(''' - print( - f'foo' - f'bar' - ) - ''', m.FStringMissingPlaceholders) - # this is an "escaped placeholder" but not a placeholder - self.flakes("f'{{}}'", m.FStringMissingPlaceholders) - # ok: f-string with placeholders - self.flakes(''' - x = 5 - print(f'{x}') - ''') - # ok: f-string with format specifiers - self.flakes(''' - x = 'a' * 90 - print(f'{x:.8}') - ''') - # ok: f-string with multiple format specifiers - self.flakes(''' - x = y = 5 - print(f'{x:>2} {y:>2}') - ''') - - def test_invalid_dot_format_calls(self): - self.flakes(''' - '{'.format(1) - - ''', m.StringDotFormatInvalidFormat) - self.flakes(''' - '{} {1}'.format(1, 2) - ''', m.StringDotFormatMixingAutomatic) - self.flakes(''' - '{0} {}'.format(1, 2) - ''', m.StringDotFormatMixingAutomatic) - self.flakes(''' - '{}'.format(1, 2) - ''', m.StringDotFormatExtraPositionalArguments) - self.flakes(''' - '{}'.format(1, bar=2) - ''', m.StringDotFormatExtraNamedArguments) - self.flakes(''' - '{} {}'.format(1) - ''', m.StringDotFormatMissingArgument) - self.flakes(''' - '{2}'.format() - ''', m.StringDotFormatMissingArgument) - self.flakes(''' - '{bar}'.format() - ''', m.StringDotFormatMissingArgument) - # too much string recursion (placeholder-in-placeholder) - self.flakes(''' - '{:{:{}}}'.format(1, 2, 3) - ''', m.StringDotFormatInvalidFormat) - # ok: dotted / bracketed names need to handle the param differently - self.flakes("'{.__class__}'.format('')") - self.flakes("'{foo[bar]}'.format(foo={'bar': 'barv'})") - # ok: placeholder-placeholders - self.flakes(''' - print('{:{}} {}'.format(1, 15, 2)) - ''') - # ok: not a placeholder-placeholder - self.flakes(''' - print('{:2}'.format(1)) - ''') - # ok: not mixed automatic - self.flakes(''' - '{foo}-{}'.format(1, foo=2) - ''') - # ok: we can't determine statically the format args - self.flakes(''' - a = () - "{}".format(*a) - ''') - self.flakes(''' - k = {} - "{foo}".format(**k) - ''') - - def test_invalid_percent_format_calls(self): - self.flakes(''' - '%(foo)' % {'foo': 'bar'} - - ''', m.PercentFormatInvalidFormat) - self.flakes(''' - '%s %(foo)s' % {'foo': 'bar'} - ''', m.PercentFormatMixedPositionalAndNamed) - self.flakes(''' - '%(foo)s %s' % {'foo': 'bar'} - ''', m.PercentFormatMixedPositionalAndNamed) - self.flakes(''' - '%j' % (1,) - ''', m.PercentFormatUnsupportedFormatCharacter) - self.flakes(''' - '%s %s' % (1,) - ''', m.PercentFormatPositionalCountMismatch) - self.flakes(''' - '%s %s' % (1, 2, 3) - ''', m.PercentFormatPositionalCountMismatch) - self.flakes(''' - '%(bar)s' % {} - ''', m.PercentFormatMissingArgument,) - self.flakes(''' - '%(bar)s' % {'bar': 1, 'baz': 2} - ''', m.PercentFormatExtraNamedArguments) - self.flakes(''' - '%(bar)s' % (1, 2, 3) - ''', m.PercentFormatExpectedMapping) - self.flakes(''' - '%s %s' % {'k': 'v'} - ''', m.PercentFormatExpectedSequence) - self.flakes(''' - '%(bar)*s' % {'bar': 'baz'} - ''', m.PercentFormatStarRequiresSequence) - # ok: single %s with mapping - self.flakes(''' - '%s' % {'foo': 'bar', 'baz': 'womp'} - ''') - # ok: does not cause a MemoryError (the strings aren't evaluated) - self.flakes(''' - "%1000000000000f" % 1 - ''') - # ok: %% should not count towards placeholder count - self.flakes(''' - '%% %s %% %s' % (1, 2) - ''') - # ok: * consumes one positional argument - self.flakes(''' - '%.*f' % (2, 1.1234) - '%*.*f' % (5, 2, 3.1234) - ''') - - def test_ok_percent_format_cannot_determine_element_count(self): - self.flakes(''' - a = [] - '%s %s' % [*a] - '%s %s' % (*a,) - - ''') - self.flakes(''' - k = {} - '%(k)s' % {**k} - ''') - - -class TestAsyncStatements(TestCase): - - @others - - ''') - - def test_asyncDefAwait(self): - self.flakes(''' - async def read_data(db): - await db.fetch('SELECT ...') - - ''') - - def test_asyncDefUndefined(self): - self.flakes(''' - async def bar(): - return foo() - - ''', m.UndefinedName) - - def test_asyncFor(self): - self.flakes(''' - async def read_data(db): - output = [] - async for row in db.cursor(): - output.append(row) - return output - - ''') - - def test_asyncForUnderscoreLoopVar(self): - self.flakes(''' - async def coro(it): - async for _ in it: - pass - - ''') - - def test_loopControlInAsyncFor(self): - self.flakes(''' - async def read_data(db): - output = [] - async for row in db.cursor(): - if row[0] == 'skip': - continue - output.append(row) - return output - - ''') - - self.flakes(''' - async def read_data(db): - output = [] - async for row in db.cursor(): - if row[0] == 'stop': - break - output.append(row) - return output - ''') - - def test_loopControlInAsyncForElse(self): - self.flakes(''' - async def read_data(db): - output = [] - async for row in db.cursor(): - output.append(row) - else: - continue - return output - - ''', m.ContinueOutsideLoop) - - self.flakes(''' - async def read_data(db): - output = [] - async for row in db.cursor(): - output.append(row) - else: - break - return output - ''', m.BreakOutsideLoop) - - def test_asyncWith(self): - self.flakes(''' - async def commit(session, data): - async with session.transaction(): - await session.update(data) - - ''') - - def test_asyncWithItem(self): - self.flakes(''' - async def commit(session, data): - async with session.transaction() as trans: - await trans.begin() - ... - await trans.end() - - ''') - - def test_matmul(self): - self.flakes(''' - def foo(a, b): - return a @ b - - ''') - - def test_formatstring(self): - self.flakes(''' - hi = 'hi' - mom = 'mom' - f'{hi} {mom}' - - ''') - - def test_raise_notimplemented(self): - self.flakes(''' - raise NotImplementedError("This is fine") - - ''') - - self.flakes(''' - raise NotImplementedError - ''') - - self.flakes(''' - raise NotImplemented("This isn't gonna work") - ''', m.RaiseNotImplemented) - - self.flakes(''' - raise NotImplemented - ''', m.RaiseNotImplemented) - - -class TestIncompatiblePrintOperator(TestCase): - """ - Tests for warning about invalid use of print function. - """ - - @others - - ''') - - def test_invalid_print_when_imported_from_future(self): - exc = self.flakes(''' - from __future__ import print_function - import sys - print >>sys.stderr, "Hello" - - ''', m.InvalidPrintSyntax).messages[0] - - self.assertEqual(exc.lineno, 4) - self.assertEqual(exc.col, 0) - - def test_print_augmented_assign(self): - # nonsense, but shouldn't crash pyflakes - self.flakes('print += 1') - - - def test_print_function_assignment(self): - """ - A valid assignment, tested for catching false positives. - """ - self.flakes(''' - from __future__ import print_function - log = print - log("Hello") - -@path pyflakes -__version__ = '3.2.0' -@language python -@tabwidth -4 - - ''') - - def test_print_in_lambda(self): - self.flakes(''' - from __future__ import print_function - a = lambda: print - - ''') - - def test_print_returned_in_function(self): - self.flakes(''' - from __future__ import print_function - def a(): - return print - - ''') - - def test_print_as_condition_test(self): - self.flakes(''' - from __future__ import print_function - if print: pass - -def test_duplicateArgs(self): - self.flakes('def fu(bar, bar): pass', m.DuplicateArgument) - - -def test_localReferencedBeforeAssignment(self): - self.flakes(''' - a = 1 - def f(): - a; a=1 - f() - -def test_unusedVariable(self): - """ - Warn when a variable in a function is assigned a value that's never - used. - """ - self.flakes(''' - def a(): - b = 1 - -def test_f_string_without_placeholders(self): - self.flakes("f'foo'", m.FStringMissingPlaceholders) - self.flakes(''' - f"""foo - bar - """ - -def test_asyncDef(self): - self.flakes(''' - async def bar(): - return 42 - -def test_valid_print(self): - self.flakes(''' - print("Hello") - -@path pyflakes/test -""" -Tests for behaviour related to type annotations. -""" - -from sys import version_info - -from pyflakes import messages as m -from pyflakes.test.harness import TestCase, skipIf - - -@others - """) -@language python -@tabwidth -4 - -class TestTypeAnnotations(TestCase): - - @others - - """) - - def test_typingExtensionsOverload(self): - """Allow intentional redefinitions via @typing_extensions.overload""" - self.flakes(""" - import typing_extensions - from typing_extensions import overload - - @overload - def f(s: None) -> None: - pass - - @overload - def f(s: int) -> int: - pass - - def f(s): - return s - - @typing_extensions.overload - def g(s: None) -> None: - pass - - @typing_extensions.overload - def g(s: int) -> int: - pass - - def g(s): - return s - - """) - - def test_typingOverloadAsync(self): - """Allow intentional redefinitions via @typing.overload (async)""" - self.flakes(""" - from typing import overload - - @overload - async def f(s: None) -> None: - pass - - @overload - async def f(s: int) -> int: - pass - - async def f(s): - return s - - """) - - def test_overload_with_multiple_decorators(self): - self.flakes(""" - from typing import overload - dec = lambda f: f - - @dec - @overload - def f(x: int) -> int: - pass - - @dec - @overload - def f(x: str) -> str: - pass - - @dec - def f(x): return x - - """) - - def test_overload_in_class(self): - self.flakes(""" - from typing import overload - - class C: - @overload - def f(self, x: int) -> int: - pass - - @overload - def f(self, x: str) -> str: - pass - - def f(self, x): return x - - """) - - def test_aliased_import(self): - """Detect when typing is imported as another name""" - self.flakes(""" - import typing as t - - @t.overload - def f(s: None) -> None: - pass - - @t.overload - def f(s: int) -> int: - pass - - def f(s): - return s - - """) - - def test_not_a_typing_overload(self): - """regression test for @typing.overload detection bug in 2.1.0""" - self.flakes(""" - def foo(x): - return x - - @foo - def bar(): - pass - - def bar(): - pass - - """, m.RedefinedWhileUnused) - - def test_variable_annotations(self): - self.flakes(''' - name: str - age: int - - ''') - self.flakes(''' - name: str = 'Bob' - age: int = 18 - ''') - self.flakes(''' - class C: - name: str - age: int - ''') - self.flakes(''' - class C: - name: str = 'Bob' - age: int = 18 - ''') - self.flakes(''' - def f(): - name: str - age: int - ''', m.UnusedAnnotation, m.UnusedAnnotation) - self.flakes(''' - def f(): - name: str = 'Bob' - age: int = 18 - foo: not_a_real_type = None - ''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable, m.UndefinedName) - self.flakes(''' - def f(): - name: str - print(name) - ''', m.UndefinedName) - self.flakes(''' - from typing import Any - def f(): - a: Any - ''', m.UnusedAnnotation) - self.flakes(''' - foo: not_a_real_type - ''', m.UndefinedName) - self.flakes(''' - foo: not_a_real_type = None - ''', m.UndefinedName) - self.flakes(''' - class C: - foo: not_a_real_type - ''', m.UndefinedName) - self.flakes(''' - class C: - foo: not_a_real_type = None - ''', m.UndefinedName) - self.flakes(''' - def f(): - class C: - foo: not_a_real_type - ''', m.UndefinedName) - self.flakes(''' - def f(): - class C: - foo: not_a_real_type = None - ''', m.UndefinedName) - self.flakes(''' - from foo import Bar - bar: Bar - ''') - self.flakes(''' - from foo import Bar - bar: 'Bar' - ''') - self.flakes(''' - import foo - bar: foo.Bar - ''') - self.flakes(''' - import foo - bar: 'foo.Bar' - ''') - self.flakes(''' - from foo import Bar - def f(bar: Bar): pass - ''') - self.flakes(''' - from foo import Bar - def f(bar: 'Bar'): pass - ''') - self.flakes(''' - from foo import Bar - def f(bar) -> Bar: return bar - ''') - self.flakes(''' - from foo import Bar - def f(bar) -> 'Bar': return bar - ''') - self.flakes(''' - bar: 'Bar' - ''', m.UndefinedName) - self.flakes(''' - bar: 'foo.Bar' - ''', m.UndefinedName) - self.flakes(''' - from foo import Bar - bar: str - ''', m.UnusedImport) - self.flakes(''' - from foo import Bar - def f(bar: str): pass - ''', m.UnusedImport) - self.flakes(''' - def f(a: A) -> A: pass - class A: pass - ''', m.UndefinedName, m.UndefinedName) - self.flakes(''' - def f(a: 'A') -> 'A': return a - class A: pass - ''') - self.flakes(''' - a: A - class A: pass - ''', m.UndefinedName) - self.flakes(''' - a: 'A' - class A: pass - ''') - self.flakes(''' - T: object - def f(t: T): pass - ''', m.UndefinedName) - self.flakes(''' - T: object - def g(t: 'T'): pass - ''') - self.flakes(''' - a: 'A B' - ''', m.ForwardAnnotationSyntaxError) - self.flakes(''' - a: 'A; B' - ''', m.ForwardAnnotationSyntaxError) - self.flakes(''' - a: '1 + 2' - ''') - self.flakes(''' - a: 'a: "A"' - ''', m.ForwardAnnotationSyntaxError) - - def test_variable_annotation_references_self_name_undefined(self): - self.flakes(""" - x: int = x - - """, m.UndefinedName) - - def test_TypeAlias_annotations(self): - self.flakes(""" - from typing_extensions import TypeAlias - from foo import Bar - - bar: TypeAlias = Bar - -def redefines(self, other): - return ( - super().redefines(other) or - (isinstance(other, Assignment) and self.name == other.name) - ) - - - - """) - self.flakes(""" - from typing_extensions import TypeAlias - from foo import Bar - - bar: TypeAlias = 'Bar' - """) - self.flakes(""" - from typing_extensions import TypeAlias - from foo import Bar - - class A: - bar: TypeAlias = Bar - """) - self.flakes(""" - from typing_extensions import TypeAlias - from foo import Bar - - class A: - bar: TypeAlias = 'Bar' - """) - self.flakes(""" - from typing_extensions import TypeAlias - - bar: TypeAlias - """) - self.flakes(""" - from typing_extensions import TypeAlias - from foo import Bar - - bar: TypeAlias - """, m.UnusedImport) - - def test_annotating_an_import(self): - self.flakes(''' - from a import b, c - b: c - print(b) - - ''') - - def test_unused_annotation(self): - # Unused annotations are fine in module and class scope - self.flakes(''' - x: int - class Cls: - y: int - - ''') - self.flakes(''' - def f(): - x: int - ''', m.UnusedAnnotation) - # This should only print one UnusedVariable message - self.flakes(''' - def f(): - x: int - x = 3 - ''', m.UnusedVariable) - - def test_unused_annotation_in_outer_scope_reassigned_in_local_scope(self): - self.flakes(''' - x: int - x.__dict__ - def f(): x = 1 - - ''', m.UndefinedName, m.UnusedVariable) - - def test_unassigned_annotation_is_undefined(self): - self.flakes(''' - name: str - print(name) - - ''', m.UndefinedName) - - def test_annotated_async_def(self): - self.flakes(''' - class c: pass - async def func(c: c) -> None: pass - - ''') - - def test_postponed_annotations(self): - self.flakes(''' - from __future__ import annotations - def f(a: A) -> A: pass - class A: - b: B - class B: pass - - ''') - - self.flakes(''' - from __future__ import annotations - def f(a: A) -> A: pass - class A: - b: Undefined - class B: pass - ''', m.UndefinedName) - - self.flakes(''' - from __future__ import annotations - T: object - def f(t: T): pass - def g(t: 'T'): pass - ''') - - def test_type_annotation_clobbers_all(self): - self.flakes('''\ - from typing import TYPE_CHECKING, List - - from y import z - - if not TYPE_CHECKING: - __all__ = ("z",) - else: - __all__: List[str] - - ''') - - def test_return_annotation_is_class_scope_variable(self): - self.flakes(""" - from typing import TypeVar - class Test: - Y = TypeVar('Y') - - def t(self, x: Y) -> Y: - return x - - """) - - def test_return_annotation_is_function_body_variable(self): - self.flakes(""" - class Test: - def t(self) -> Y: - Y = 2 - return Y - - """, m.UndefinedName) - - def test_positional_only_argument_annotations(self): - self.flakes(""" - from x import C - - def f(c: C, /): ... - -def __init__(self, name): - super().__init__(name, None) - - - """) - - def test_partially_quoted_type_annotation(self): - self.flakes(""" - from queue import Queue - from typing import Optional - - def f() -> Optional['Queue[str]']: - return None - - """) - - def test_partially_quoted_type_assignment(self): - self.flakes(""" - from queue import Queue - from typing import Optional - - MaybeQueue = Optional['Queue[str]'] - - """) - - def test_nested_partially_quoted_type_assignment(self): - self.flakes(""" - from queue import Queue - from typing import Callable - - Func = Callable[['Queue[str]'], None] - - """) - - def test_quoted_type_cast(self): - self.flakes(""" - from typing import cast, Optional - - maybe_int = cast('Optional[int]', 42) - - """) - - def test_type_cast_literal_str_to_str(self): - # Checks that our handling of quoted type annotations in the first - # argument to `cast` doesn't cause issues when (only) the _second_ - # argument is a literal str which looks a bit like a type annotation. - self.flakes(""" - from typing import cast - - a_string = cast(str, 'Optional[int]') - - """) - - def test_quoted_type_cast_renamed_import(self): - self.flakes(""" - from typing import cast as tsac, Optional as Maybe - - maybe_int = tsac('Maybe[int]', 42) - - """) - - def test_quoted_TypeVar_constraints(self): - self.flakes(""" - from typing import TypeVar, Optional - - T = TypeVar('T', 'str', 'Optional[int]', bytes) - - """) - - def test_quoted_TypeVar_bound(self): - self.flakes(""" - from typing import TypeVar, Optional, List - - T = TypeVar('T', bound='Optional[int]') - S = TypeVar('S', int, bound='List[int]') - - """) - - def test_literal_type_typing(self): - self.flakes(""" - from typing import Literal - - def f(x: Literal['some string']) -> None: - return None - - """) - - def test_literal_type_typing_extensions(self): - self.flakes(""" - from typing_extensions import Literal - - def f(x: Literal['some string']) -> None: - return None - -def __repr__(self): - return '<{} object {!r} at 0x{:x}>'.format( - self.__class__.__name__, - self.name, - id(self) - ) - - - - """) - - def test_annotated_type_typing_missing_forward_type(self): - self.flakes(""" - from typing import Annotated - - def f(x: Annotated['integer']) -> None: - return None - - """, m.UndefinedName) - - def test_annotated_type_typing_missing_forward_type_multiple_args(self): - self.flakes(""" - from typing import Annotated - - def f(x: Annotated['integer', 1]) -> None: - return None - - """, m.UndefinedName) - - def test_annotated_type_typing_with_string_args(self): - self.flakes(""" - from typing import Annotated - - def f(x: Annotated[int, '> 0']) -> None: - return None - - """) - - def test_annotated_type_typing_with_string_args_in_union(self): - self.flakes(""" - from typing import Annotated, Union - - def f(x: Union[Annotated['int', '>0'], 'integer']) -> None: - return None - - """, m.UndefinedName) - - def test_literal_type_some_other_module(self): - """err on the side of false-negatives for types named Literal""" - self.flakes(""" - from my_module import compat - from my_module.compat import Literal - - def f(x: compat.Literal['some string']) -> None: - return None - def g(x: Literal['some string']) -> None: - return None - - """) - - def test_literal_union_type_typing(self): - self.flakes(""" - from typing import Literal - - def f(x: Literal['some string', 'foo bar']) -> None: - return None - - """) - - def test_deferred_twice_annotation(self): - self.flakes(""" - from queue import Queue - from typing import Optional - - - def f() -> "Optional['Queue[str]']": - return None - - """) - - def test_partial_string_annotations_with_future_annotations(self): - self.flakes(""" - from __future__ import annotations - - from queue import Queue - from typing import Optional - - - def f() -> Optional['Queue[str]']: - return None - - """) - - def test_forward_annotations_for_classes_in_scope(self): - # see #749 - self.flakes(""" - from typing import Optional - - def f(): - class C: - a: "D" - b: Optional["D"] - c: "Optional[D]" - - class D: pass - - """) - - def test_idomiatic_typing_guards(self): - # typing.TYPE_CHECKING: python3.5.3+ - self.flakes(""" - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from t import T - - def f() -> T: - pass - -def __init__(self, item): - self.name = item.id - - - """) - # False: the old, more-compatible approach - self.flakes(""" - if False: - from t import T - - def f() -> T: - pass - """) - # some choose to assign a constant and do it that way - self.flakes(""" - MYPY = False - - if MYPY: - from t import T - - def f() -> T: - pass - """) - - def test_typing_guard_for_protocol(self): - self.flakes(""" - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from typing import Protocol - else: - Protocol = object - - class C(Protocol): - def f() -> int: - pass - - """) - - def test_typednames_correct_forward_ref(self): - self.flakes(""" - from typing import TypedDict, List, NamedTuple - - List[TypedDict("x", {})] - List[TypedDict("x", x=int)] - List[NamedTuple("a", a=int)] - List[NamedTuple("a", [("a", int)])] - - """) - self.flakes(""" - from typing import TypedDict, List, NamedTuple, TypeVar - - List[TypedDict("x", {"x": "Y"})] - List[TypedDict("x", x="Y")] - List[NamedTuple("a", [("a", "Y")])] - List[NamedTuple("a", a="Y")] - List[TypedDict("x", {"x": List["a"]})] - List[TypeVar("A", bound="C")] - List[TypeVar("A", List["C"])] - """, *[m.UndefinedName]*7) - self.flakes(""" - from typing import NamedTuple, TypeVar, cast - from t import A, B, C, D, E - - NamedTuple("A", [("a", A["C"])]) - TypeVar("A", bound=A["B"]) - TypeVar("A", A["D"]) - cast(A["E"], []) - """) - - def test_namedtypes_classes(self): - self.flakes(""" - from typing import TypedDict, NamedTuple - class X(TypedDict): - y: TypedDict("z", {"zz":int}) - - class Y(NamedTuple): - y: NamedTuple("v", [("vv", int)]) - - """) - - @skipIf(version_info < (3, 11), 'new in Python 3.11') - def test_variadic_generics(self): - self.flakes(""" - from typing import Generic - from typing import TypeVarTuple - - Ts = TypeVarTuple('Ts') - - class Shape(Generic[*Ts]): pass - - def f(*args: *Ts) -> None: ... - - def g(x: Shape[*Ts]) -> Shape[*Ts]: ... - - """) - - @skipIf(version_info < (3, 12), 'new in Python 3.12') - def test_type_statements(self): - self.flakes(""" - type ListOrSet[T] = list[T] | set[T] - - def f(x: ListOrSet[str]) -> None: ... - - type RecursiveType = int | list[RecursiveType] - - type ForwardRef = int | C - - type ForwardRefInBounds[T: C] = T - - class C: pass - - """) - - @skipIf(version_info < (3, 12), 'new in Python 3.12') - def test_type_parameters_functions(self): - self.flakes(""" - def f[T](t: T) -> T: return t - - async def g[T](t: T) -> T: return t - - def with_forward_ref[T: C](t: T) -> T: return t - - def can_access_inside[T](t: T) -> T: - print(T) - return t - - class C: pass - - """) - - @skipIf(version_info < (3, 12), 'new in Python 3.12') - def test_type_parameters_do_not_escape_function_scopes(self): - self.flakes(""" - from x import g - - @g(T) # not accessible in decorators - def f[T](t: T) -> T: return t - - T # not accessible afterwards - - """, m.UndefinedName, m.UndefinedName) - - @skipIf(version_info < (3, 12), 'new in Python 3.12') - def test_type_parameters_classes(self): - self.flakes(""" - class C[T](list[T]): pass - - class UsesForward[T: Forward](list[T]): pass - - class Forward: pass - - class WithinBody[T](list[T]): - t = T - - """) - - @skipIf(version_info < (3, 12), 'new in Python 3.12') - def test_type_parameters_do_not_escape_class_scopes(self): - self.flakes(""" - from x import g - - @g(T) # not accessible in decorators - class C[T](list[T]): pass - - T # not accessible afterwards - - """, m.UndefinedName, m.UndefinedName) - - @skipIf(version_info < (3, 12), 'new in Python 3.12') - def test_type_parameters_TypeVarTuple(self): - self.flakes(""" - def f[*T](*args: *T) -> None: ... - -def __eq__(self, compare): - return ( - compare.__class__ == self.__class__ and - compare.name == self.name - ) - - - """) - - @skipIf(version_info < (3, 12), 'new in Python 3.12') - def test_type_parameters_ParamSpec(self): - self.flakes(""" - from typing import Callable - - def f[R, **P](f: Callable[P, R]) -> Callable[P, R]: - def g(*args: P.args, **kwargs: P.kwargs) -> R: - return f(*args, **kwargs) - return g - -def test_typingOverload(self): - """Allow intentional redefinitions via @typing.overload""" - self.flakes(""" - import typing - from typing import overload - - @overload - def f(s: None) -> None: - pass - - @overload - def f(s: int) -> int: - pass - - def f(s): - return s - - @typing.overload - def g(s: None) -> None: - pass - - @typing.overload - def g(s: int) -> int: - pass - - def g(s): - return s - -@path pyflakes/test -import ast - -from pyflakes import messages as m, checker -from pyflakes.test.harness import TestCase, skip - - -@others -@language python -@tabwidth -4 - -class Test(TestCase): - @others - - ''', - m.UndefinedName) - - def test_undefinedExceptionName(self): - """Exception names can't be used after the except: block. - - The exc variable is unused inside the exception handler.""" - self.flakes(''' - try: - raise ValueError('ve') - except ValueError as exc: - pass - exc - - ''', m.UndefinedName, m.UnusedVariable) - - def test_namesDeclaredInExceptBlocks(self): - """Locals declared in except: blocks can be used after the block. - - This shows the example in test_undefinedExceptionName is - different.""" - self.flakes(''' - try: - raise ValueError('ve') - except ValueError as exc: - e = exc - e - - ''') - - @skip('error reporting disabled due to false positives below') - def test_undefinedExceptionNameObscuringLocalVariable(self): - """Exception names obscure locals, can't be used after. - - Last line will raise UnboundLocalError on Python 3 after exiting - the except: block. Note next two examples for false positives to - watch out for.""" - self.flakes(''' - exc = 'Original value' - try: - raise ValueError('ve') - except ValueError as exc: - pass - exc - - ''', - m.UndefinedName) - - def test_undefinedExceptionNameObscuringLocalVariable2(self): - """Exception names are unbound after the `except:` block. - - Last line will raise UnboundLocalError. - The exc variable is unused inside the exception handler. - """ - self.flakes(''' - try: - raise ValueError('ve') - except ValueError as exc: - pass - print(exc) - exc = 'Original value' - - ''', m.UndefinedName, m.UnusedVariable) - - def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self): - """Exception names obscure locals, can't be used after. Unless. - - Last line will never raise UnboundLocalError because it's only - entered if no exception was raised.""" - self.flakes(''' - exc = 'Original value' - try: - raise ValueError('ve') - except ValueError as exc: - print('exception logged') - raise - exc - - ''', m.UnusedVariable) - - def test_delExceptionInExcept(self): - """The exception name can be deleted in the except: block.""" - self.flakes(''' - try: - pass - except Exception as exc: - del exc - -def __hash__(self): - return hash(self.name) - - - - ''') - - def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self): - """Exception names obscure locals, can't be used after. Unless. - - Last line will never raise UnboundLocalError because `error` is - only falsy if the `except:` block has not been entered.""" - self.flakes(''' - exc = 'Original value' - error = None - try: - raise ValueError('ve') - except ValueError as exc: - error = 'exception logged' - if error: - print(error) - else: - exc - - ''', m.UnusedVariable) - - @skip('error reporting disabled due to false positives below') - def test_undefinedExceptionNameObscuringGlobalVariable(self): - """Exception names obscure globals, can't be used after. - - Last line will raise UnboundLocalError because the existence of that - exception name creates a local scope placeholder for it, obscuring any - globals, etc.""" - self.flakes(''' - exc = 'Original value' - def func(): - try: - pass # nothing is raised - except ValueError as exc: - pass # block never entered, exc stays unbound - exc - - ''', - m.UndefinedLocal) - - @skip('error reporting disabled due to false positives below') - def test_undefinedExceptionNameObscuringGlobalVariable2(self): - """Exception names obscure globals, can't be used after. - - Last line will raise NameError on Python 3 because the name is - locally unbound after the `except:` block, even if it's - nonlocal. We should issue an error in this case because code - only working correctly if an exception isn't raised, is invalid. - Unless it's explicitly silenced, see false positives below.""" - self.flakes(''' - exc = 'Original value' - def func(): - global exc - try: - raise ValueError('ve') - except ValueError as exc: - pass # block never entered, exc stays unbound - exc - - ''', - m.UndefinedLocal) - - def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1(self): - """Exception names obscure globals, can't be used after. Unless. - - Last line will never raise NameError because it's only entered - if no exception was raised.""" - self.flakes(''' - exc = 'Original value' - def func(): - global exc - try: - raise ValueError('ve') - except ValueError as exc: - print('exception logged') - raise - exc - - ''', m.UnusedVariable) - - def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2(self): - """Exception names obscure globals, can't be used after. Unless. - - Last line will never raise NameError because `error` is only - falsy if the `except:` block has not been entered.""" - self.flakes(''' - exc = 'Original value' - def func(): - global exc - error = None - try: - raise ValueError('ve') - except ValueError as exc: - error = 'exception logged' - if error: - print(error) - else: - exc - - ''', m.UnusedVariable) - - def test_functionsNeedGlobalScope(self): - self.flakes(''' - class a: - def b(): - fu - fu = 1 - - ''') - - def test_builtins(self): - self.flakes('range(10)') - - - def test_builtinWindowsError(self): - """ - C{WindowsError} is sometimes a builtin name, so no warning is emitted - for using it. - """ - self.flakes('WindowsError') - - - def test_moduleAnnotations(self): - """ - Use of the C{__annotations__} in module scope should not emit - an undefined name warning when version is greater than or equal to 3.6. - """ - self.flakes('__annotations__') - - - def test_magicGlobalsFile(self): - """ - Use of the C{__file__} magic global should not emit an undefined name - warning. - """ - self.flakes('__file__') - - -def __init__(self, name, source, full_name=None): - self.fullName = full_name or name - self.redefined = [] - super().__init__(name, source) - - - def test_magicGlobalsBuiltins(self): - """ - Use of the C{__builtins__} magic global should not emit an undefined - name warning. - """ - self.flakes('__builtins__') - - - def test_magicGlobalsName(self): - """ - Use of the C{__name__} magic global should not emit an undefined name - warning. - """ - self.flakes('__name__') - - - def test_magicGlobalsPath(self): - """ - Use of the C{__path__} magic global should not emit an undefined name - warning, if you refer to it from a file called __init__.py. - """ - self.flakes('__path__', m.UndefinedName) - self.flakes('__path__', filename='package/__init__.py') - - - def test_magicModuleInClassScope(self): - """ - Use of the C{__module__} magic builtin should not emit an undefined - name warning if used in class scope. - """ - self.flakes('__module__', m.UndefinedName) - self.flakes(''' - class Foo: - __module__ - - ''') - self.flakes(''' - class Foo: - def bar(self): - __module__ - ''', m.UndefinedName) - - def test_magicQualnameInClassScope(self): - """ - Use of the C{__qualname__} magic builtin should not emit an undefined - name warning if used in class scope. - """ - self.flakes('__qualname__', m.UndefinedName) - self.flakes(''' - class Foo: - __qualname__ - - ''') - self.flakes(''' - class Foo: - def bar(self): - __qualname__ - ''', m.UndefinedName) - - def test_globalImportStar(self): - """Can't find undefined names with import *.""" - self.flakes('from fu import *; bar', - m.ImportStarUsed, m.ImportStarUsage) - - - def test_definedByGlobal(self): - """ - "global" can make an otherwise undefined name in another function - defined. - """ - self.flakes(''' - def a(): global fu; fu = 1 - def b(): fu - - ''') - self.flakes(''' - def c(): bar - def b(): global bar; bar = 1 - ''') - - def test_definedByGlobalMultipleNames(self): - """ - "global" can accept multiple names. - """ - self.flakes(''' - def a(): global fu, bar; fu = 1; bar = 2 - def b(): fu; bar - - ''') - - def test_globalInGlobalScope(self): - """ - A global statement in the global scope is ignored. - """ - self.flakes(''' - global x - def foo(): - print(x) - - ''', m.UndefinedName) - - def test_global_reset_name_only(self): - """A global statement does not prevent other names being undefined.""" - # Only different undefined names are reported. - # See following test that fails where the same name is used. - self.flakes(''' - def f1(): - s - - def f2(): - global m - -def redefines(self, other): - if isinstance(other, SubmoduleImportation): - # See note in SubmoduleImportation about RedefinedWhileUnused - return self.fullName == other.fullName - return isinstance(other, Definition) and self.name == other.name - - - ''', m.UndefinedName) - - @skip("todo") - def test_unused_global(self): - """An unused global statement does not define the name.""" - self.flakes(''' - def f1(): - m - - def f2(): - global m - - ''', m.UndefinedName) - - def test_del(self): - """Del deletes bindings.""" - self.flakes('a = 1; del a; a', m.UndefinedName) - - - def test_delGlobal(self): - """Del a global binding from a function.""" - self.flakes(''' - a = 1 - def f(): - global a - del a - a - - ''') - - def test_delUndefined(self): - """Del an undefined name.""" - self.flakes('del a', m.UndefinedName) - - - def test_delConditional(self): - """ - Ignores conditional bindings deletion. - """ - self.flakes(''' - context = None - test = True - if False: - del(test) - assert(test) - - ''') - - def test_delConditionalNested(self): - """ - Ignored conditional bindings deletion even if they are nested in other - blocks. - """ - self.flakes(''' - context = None - test = True - if False: - with context(): - del(test) - assert(test) - - ''') - - def test_delWhile(self): - """ - Ignore bindings deletion if called inside the body of a while - statement. - """ - self.flakes(''' - def test(): - foo = 'bar' - while False: - del foo - assert(foo) - - ''') - - def test_delWhileTestUsage(self): - """ - Ignore bindings deletion if called inside the body of a while - statement and name is used inside while's test part. - """ - self.flakes(''' - def _worker(): - o = True - while o is not True: - del o - o = False - - ''') - - def test_delWhileNested(self): - """ - Ignore bindings deletions if node is part of while's test, even when - del is in a nested block. - """ - self.flakes(''' - context = None - def _worker(): - o = True - while o is not True: - while True: - with context(): - del o - o = False - - ''') - - def test_globalFromNestedScope(self): - """Global names are available from nested scopes.""" - self.flakes(''' - a = 1 - def b(): - def c(): - a - -@path pyflakes -from pyflakes.api import main - -# python -m pyflakes -if __name__ == '__main__': - main(prog='pyflakes') -@language python -@tabwidth -4 - -def _has_alias(self): - """Return whether importation needs an as clause.""" - return not self.fullName.split('.')[-1] == self.name - - - ''') - - def test_laterRedefinedGlobalFromNestedScope(self): - """ - Test that referencing a local name that shadows a global, before it is - defined, generates a warning. - """ - self.flakes(''' - a = 1 - def fun(): - a - a = 2 - return a - - ''', m.UndefinedLocal) - - def test_laterRedefinedGlobalFromNestedScope2(self): - """ - Test that referencing a local name in a nested scope that shadows a - global declared in an enclosing scope, before it is defined, generates - a warning. - """ - self.flakes(''' - a = 1 - def fun(): - global a - def fun2(): - a - a = 2 - return a - - ''', m.UndefinedLocal) - - def test_intermediateClassScopeIgnored(self): - """ - If a name defined in an enclosing scope is shadowed by a local variable - and the name is used locally before it is bound, an unbound local - warning is emitted, even if there is a class scope between the enclosing - scope and the local scope. - """ - self.flakes(''' - def f(): - x = 1 - class g: - def h(self): - a = x - x = None - print(x, a) - print(x) - - ''', m.UndefinedLocal) - - def test_doubleNestingReportsClosestName(self): - """ - Test that referencing a local name in a nested scope that shadows a - variable declared in two different outer scopes before it is defined - in the innermost scope generates an UnboundLocal warning which - refers to the nearest shadowed name. - """ - exc = self.flakes(''' - def a(): - x = 1 - def b(): - x = 2 # line 5 - def c(): - x - x = 3 - return x - return x - return x - - ''', m.UndefinedLocal).messages[0] - - # _DoctestMixin.flakes adds two lines preceding the code above. - expected_line_num = 7 if self.withDoctest else 5 - - self.assertEqual(exc.message_args, ('x', expected_line_num)) - - def test_laterRedefinedGlobalFromNestedScope3(self): - """ - Test that referencing a local name in a nested scope that shadows a - global, before it is defined, generates a warning. - """ - self.flakes(''' - def fun(): - a = 1 - def fun2(): - a - a = 1 - return a - return a - - ''', m.UndefinedLocal) - - def test_undefinedAugmentedAssignment(self): - self.flakes( - ''' - def f(seq): - a = 0 - seq[a] += 1 - seq[b] /= 2 - c[0] *= 2 - a -= 3 - d += 4 - e[any] = 5 - - ''', - m.UndefinedName, # b - m.UndefinedName, # c - m.UndefinedName, m.UnusedVariable, # d - m.UndefinedName, # e - ) - - def test_nestedClass(self): - """Nested classes can access enclosing scope.""" - self.flakes(''' - def f(foo): - class C: - bar = foo - def f(self): - return foo - return C() - - f(123).f() - - ''') - - def test_badNestedClass(self): - """Free variables in nested classes must bind at class creation.""" - self.flakes(''' - def f(): - class C: - bar = foo - foo = 456 - return foo - f() - - ''', m.UndefinedName) - - def test_definedAsStarArgs(self): - """Star and double-star arg names are defined.""" - self.flakes(''' - def f(a, *b, **c): - print(a, b, c) - - ''') - - def test_definedAsStarUnpack(self): - """Star names in unpack are defined.""" - self.flakes(''' - a, *b = range(10) - print(a, b) - -@property -def source_statement(self): - """Generate a source statement equivalent to the import.""" - if self._has_alias(): - return f'import {self.fullName} as {self.name}' - else: - return 'import %s' % self.fullName - - - ''') - self.flakes(''' - *a, b = range(10) - print(a, b) - ''') - self.flakes(''' - a, *b, c = range(10) - print(a, b, c) - ''') - - def test_usedAsStarUnpack(self): - """ - Star names in unpack are used if RHS is not a tuple/list literal. - """ - self.flakes(''' - def f(): - a, *b = range(10) - - ''') - self.flakes(''' - def f(): - (*a, b) = range(10) - ''') - self.flakes(''' - def f(): - [a, *b, c] = range(10) - ''') - - def test_unusedAsStarUnpack(self): - """ - Star names in unpack are unused if RHS is a tuple/list literal. - """ - self.flakes(''' - def f(): - a, *b = any, all, 4, 2, 'un' - - ''', m.UnusedVariable, m.UnusedVariable) - self.flakes(''' - def f(): - (*a, b) = [bool, int, float, complex] - ''', m.UnusedVariable, m.UnusedVariable) - self.flakes(''' - def f(): - [a, *b, c] = 9, 8, 7, 6, 5, 4 - ''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable) - - def test_keywordOnlyArgs(self): - """Keyword-only arg names are defined.""" - self.flakes(''' - def f(*, a, b=None): - print(a, b) - - ''') - - self.flakes(''' - import default_b - def f(*, a, b=default_b): - print(a, b) - ''') - - def test_keywordOnlyArgsUndefined(self): - """Typo in kwonly name.""" - self.flakes(''' - def f(*, a, b=default_c): - print(a, b) - - ''', m.UndefinedName) - - def test_annotationUndefined(self): - """Undefined annotations.""" - self.flakes(''' - from abc import note1, note2, note3, note4, note5 - def func(a: note1, *args: note2, - b: note3=12, **kw: note4) -> note5: pass - - ''') - - self.flakes(''' - def func(): - d = e = 42 - def func(a: {1, d}) -> (lambda c: e): pass - ''') - - def test_metaClassUndefined(self): - self.flakes(''' - from abc import ABCMeta - class A(metaclass=ABCMeta): pass - - ''') - - def test_definedInGenExp(self): - """ - Using the loop variable of a generator expression results in no - warnings. - """ - self.flakes('(a for a in [1, 2, 3] if a)') - - self.flakes('(b for b in (a for a in [1, 2, 3] if a) if b)') - - - def test_undefinedInGenExpNested(self): - """ - The loop variables of generator expressions nested together are - not defined in the other generator. - """ - self.flakes('(b for b in (a for a in [1, 2, 3] if b) if b)', - m.UndefinedName) - - self.flakes('(b for b in (a for a in [1, 2, 3] if a) if a)', - m.UndefinedName) - - - def test_undefinedWithErrorHandler(self): - """ - Some compatibility code checks explicitly for NameError. - It should not trigger warnings. - """ - self.flakes(''' - try: - socket_map - except NameError: - socket_map = {} - - ''') - self.flakes(''' - try: - _memoryview.contiguous - except (NameError, AttributeError): - raise RuntimeError("Python >= 3.3 is required") - ''') - # If NameError is not explicitly handled, generate a warning - self.flakes(''' - try: - socket_map - except: - socket_map = {} - ''', m.UndefinedName) - self.flakes(''' - try: - socket_map - except Exception: - socket_map = {} - ''', m.UndefinedName) - - def test_definedInClass(self): - """ - Defined name for generator expressions and dict/set comprehension. - """ - self.flakes(''' - class A: - T = range(10) - - Z = (x for x in T) - L = [x for x in T] - B = dict((i, str(i)) for i in T) - -def __str__(self): - """Return import full name with alias.""" - if self._has_alias(): - return self.fullName + ' as ' + self.name - else: - return self.fullName - - - - ''') - - self.flakes(''' - class A: - T = range(10) - - X = {x for x in T} - Y = {x:x for x in T} - ''') - - def test_definedInClassNested(self): - """Defined name for nested generator expressions in a class.""" - self.flakes(''' - class A: - T = range(10) - - Z = (x for x in (a for a in T)) - - ''') - - def test_undefinedInLoop(self): - """ - The loop variable is defined after the expression is computed. - """ - self.flakes(''' - for i in range(i): - print(i) - - ''', m.UndefinedName) - self.flakes(''' - [42 for i in range(i)] - ''', m.UndefinedName) - self.flakes(''' - (42 for i in range(i)) - ''', m.UndefinedName) - - def test_definedFromLambdaInDictionaryComprehension(self): - """ - Defined name referenced from a lambda function within a dict/set - comprehension. - """ - self.flakes(''' - {lambda: id(x) for x in range(10)} - - ''') - - def test_definedFromLambdaInGenerator(self): - """ - Defined name referenced from a lambda function within a generator - expression. - """ - self.flakes(''' - any(lambda: id(x) for x in range(10)) - - ''') - - def test_undefinedFromLambdaInDictionaryComprehension(self): - """ - Undefined name referenced from a lambda function within a dict/set - comprehension. - """ - self.flakes(''' - {lambda: id(y) for x in range(10)} - - ''', m.UndefinedName) - - def test_undefinedFromLambdaInComprehension(self): - """ - Undefined name referenced from a lambda function within a generator - expression. - """ - self.flakes(''' - any(lambda: id(y) for x in range(10)) - - ''', m.UndefinedName) - - def test_dunderClass(self): - code = ''' - class Test(object): - def __init__(self): - print(__class__.__name__) - self.x = 1 - - t = Test() - ''' - self.flakes(code) - - - -class NameTests(TestCase): - """ - Tests for some extra cases of name handling. - """ - @others - -def test_undefined(self): - self.flakes('bar', m.UndefinedName) - - -def test_definedInListComp(self): - self.flakes('[a for a in range(10) if a]') - - -def __init__(self, name, source): - # A dot should only appear in the name when it is a submodule import - assert '.' in name and (not source or isinstance(source, ast.Import)) - package_name = name.split('.')[0] - super().__init__(package_name, source) - self.fullName = name - - -def test_undefinedInListComp(self): - self.flakes(''' - [a for a in range(10)] - a - -def test_impossibleContext(self): - """ - A Name node with an unrecognized context results in a RuntimeError being - raised. - """ - tree = ast.parse("x = 10") - # Make it into something unrecognizable. - tree.body[0].targets[0].ctx = object() - self.assertRaises(RuntimeError, checker.Checker, tree) - -def redefines(self, other): - if isinstance(other, Importation): - return self.fullName == other.fullName - return super().redefines(other) - - -def __str__(self): - return self.fullName - - -@property -def source_statement(self): - return 'import ' + self.fullName - - - -def __init__(self, name, source, module, real_name=None): - self.module = module - self.real_name = real_name or name - - if module.endswith('.'): - full_name = module + self.real_name - else: - full_name = module + '.' + self.real_name - - super().__init__(name, source, full_name) - - -def __str__(self): - """Return import full name with alias.""" - if self.real_name != self.name: - return self.fullName + ' as ' + self.name - else: - return self.fullName - - -@property -def source_statement(self): - if self.real_name != self.name: - return f'from {self.module} import {self.real_name} as {self.name}' - else: - return f'from {self.module} import {self.name}' - - - -@path pyflakes -""" -API for the command-line I{pyflakes} tool. -""" -import ast -import os -import platform -import re -import sys - -from pyflakes import checker, __version__ -from pyflakes import reporter as modReporter - -__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main'] - -PYTHON_SHEBANG_REGEX = re.compile(br'^#!.*\bpython(3(\.\d+)?|w)?[dmu]?\s') - - -@others -@language python -@tabwidth -4 - -def __init__(self, name, source): - super().__init__('*', source) - # Each star importation needs a unique name, and - # may not be the module name otherwise it will be deemed imported - self.name = name + '.*' - self.fullName = name - - -@property -def source_statement(self): - return 'from ' + self.fullName + ' import *' - - -def __str__(self): - # When the module ends with a ., avoid the ambiguous '..*' - if self.fullName.endswith('.'): - return self.source_statement - else: - return self.name - - - -def __init__(self, name, source, scope): - super().__init__(name, source, '__future__') - self.used = (scope, source) - - - -def redefines(self, other): - """An Annotation doesn't define any name, so it cannot redefine one.""" - return False - - - -def __init__(self, name, source, scope): - if '__all__' in scope and isinstance(source, ast.AugAssign): - self.names = list(scope['__all__'].names) - else: - self.names = [] - - def _add_to_names(container): - for node in container.elts: - if isinstance(node, ast.Constant) and isinstance(node.value, str): - self.names.append(node.value) - - if isinstance(source.value, (ast.List, ast.Tuple)): - _add_to_names(source.value) - # If concatenating lists or tuples - elif isinstance(source.value, ast.BinOp): - currentValue = source.value - while isinstance(currentValue.right, (ast.List, ast.Tuple)): - left = currentValue.left - right = currentValue.right - _add_to_names(right) - # If more lists are being added - if isinstance(left, ast.BinOp): - currentValue = left - # If just two lists are being added - elif isinstance(left, (ast.List, ast.Tuple)): - _add_to_names(left) - # All lists accounted for - done - break - # If not list concatenation - else: - break - super().__init__(name, source) - - - -usesLocals = False -alwaysUsed = {'__tracebackhide__', '__traceback_info__', - '__traceback_supplement__', '__debuggerskip__'} - -def __init__(self): - super().__init__() - # Simplify: manage the special locals as globals - self.globals = self.alwaysUsed.copy() - self.returnValue = None # First non-empty return - - -def unused_assignments(self): - """ - Return a generator for the assignments which have not been used. - """ - for name, binding in self.items(): - if (not binding.used and - name != '_' and # see issue #202 - name not in self.globals and - not self.usesLocals and - isinstance(binding, Assignment)): - yield name, binding - - -def unused_annotations(self): - """ - Return a generator for the annotations which have not been used. - """ - for name, binding in self.items(): - if not binding.used and isinstance(binding, Annotation): - yield name, binding - - - - - -@language rest -@wrap - -Goals: -- Understand pyflakes. -- Add global code that trusts annotations. - -@language python - - - -FOR_TYPES = (ast.For, ast.AsyncFor) - - - -MAPPING_KEY_RE = re.compile(r'\(([^()]*)\)') -CONVERSION_FLAG_RE = re.compile('[#0+ -]*') -WIDTH_RE = re.compile(r'(?:\*|\d*)') -PRECISION_RE = re.compile(r'(?:\.(?:\*|\d*))?') -LENGTH_RE = re.compile('[hlL]?') -# https://docs.python.org/3/library/stdtypes.html#old-string-formatting -VALID_CONVERSIONS = frozenset('diouxXeEfFgGcrsa%') - - - -# Globally defined names which are not attributes of the builtins module, or -# are only present on some platforms. -_MAGIC_GLOBALS = ['__file__', '__builtins__', '__annotations__', 'WindowsError'] - - - -TYPING_MODULES = frozenset(('typing', 'typing_extensions')) - - - - - - - -_ast_node_scope = { - ast.Module: ModuleScope, - ast.ClassDef: ClassScope, - ast.FunctionDef: FunctionScope, - ast.AsyncFunctionDef: FunctionScope, - ast.Lambda: FunctionScope, - ast.ListComp: GeneratorScope, - ast.SetComp: GeneratorScope, - ast.GeneratorExp: GeneratorScope, - ast.DictComp: GeneratorScope, -} - -nodeDepth = 0 -offset = None -_in_annotation = AnnotationState.NONE - -builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) -_customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') -if _customBuiltIns: - builtIns.update(_customBuiltIns.split(',')) -del _customBuiltIns - - - - - -PYPY = hasattr(sys, 'pypy_version_info') - -builtin_vars = dir(builtins) - -parse_format_string = string.Formatter().parse - - -@nosearch - -# Word, Head, Body - -# found 7 nodes -# additional node types -COMPREHENSION = KEYWORD = FORMATTEDVALUE = handleChildren - - - - - - -@nosearch - -# Word, Head, Body - -# found 22 nodes - - - - -