1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570 |
- #!/usr/bin/env python
-
- # Copyright (C) 2010-2011 Hideo Hattori
- # Copyright (C) 2011-2013 Hideo Hattori, Steven Myint
- # Copyright (C) 2013-2016 Hideo Hattori, Steven Myint, Bill Wendling
- #
- # Permission is hereby granted, free of charge, to any person obtaining
- # a copy of this software and associated documentation files (the
- # "Software"), to deal in the Software without restriction, including
- # without limitation the rights to use, copy, modify, merge, publish,
- # distribute, sublicense, and/or sell copies of the Software, and to
- # permit persons to whom the Software is furnished to do so, subject to
- # the following conditions:
- #
- # The above copyright notice and this permission notice shall be
- # included in all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- # SOFTWARE.
-
- # Copyright (C) 2006-2009 Johann C. Rocholl <johann@rocholl.net>
- # Copyright (C) 2009-2013 Florent Xicluna <florent.xicluna@gmail.com>
- #
- # Permission is hereby granted, free of charge, to any person
- # obtaining a copy of this software and associated documentation files
- # (the "Software"), to deal in the Software without restriction,
- # including without limitation the rights to use, copy, modify, merge,
- # publish, distribute, sublicense, and/or sell copies of the Software,
- # and to permit persons to whom the Software is furnished to do so,
- # subject to the following conditions:
- #
- # The above copyright notice and this permission notice shall be
- # included in all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- # SOFTWARE.
-
- """Automatically formats Python code to conform to the PEP 8 style guide.
-
- Fixes that only need be done once can be added by adding a function of the form
- "fix_<code>(source)" to this module. They should return the fixed source code.
- These fixes are picked up by apply_global_fixes().
-
- Fixes that depend on pycodestyle should be added as methods to FixPEP8. See the
- class documentation for more information.
-
- """
-
- from __future__ import absolute_import
- from __future__ import division
- from __future__ import print_function
- from __future__ import unicode_literals
-
- import argparse
- import codecs
- import collections
- import copy
- import difflib
- import fnmatch
- import inspect
- import io
- import itertools
- import keyword
- import locale
- import os
- import re
- import signal
- import sys
- import textwrap
- import token
- import tokenize
- import warnings
- import ast
- from configparser import ConfigParser as SafeConfigParser, Error
-
- import pycodestyle
- from pycodestyle import STARTSWITH_INDENT_STATEMENT_REGEX
-
-
- __version__ = '2.0.2'
-
-
- CR = '\r'
- LF = '\n'
- CRLF = '\r\n'
-
-
- PYTHON_SHEBANG_REGEX = re.compile(r'^#!.*\bpython[23]?\b\s*$')
- LAMBDA_REGEX = re.compile(r'([\w.]+)\s=\slambda\s*([)(=\w,\s.]*):')
- COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+([^][)(}{]+?)\s+(in|is)\s')
- COMPARE_NEGATIVE_REGEX_THROUGH = re.compile(r'\b(not\s+in|is\s+not)\s')
- BARE_EXCEPT_REGEX = re.compile(r'except\s*:')
- STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\s.*\):')
- DOCSTRING_START_REGEX = re.compile(r'^u?r?(?P<kind>["\']{3})')
- ENABLE_REGEX = re.compile(r'# *(fmt|autopep8): *on')
- DISABLE_REGEX = re.compile(r'# *(fmt|autopep8): *off')
-
- EXIT_CODE_OK = 0
- EXIT_CODE_ERROR = 1
- EXIT_CODE_EXISTS_DIFF = 2
- EXIT_CODE_ARGPARSE_ERROR = 99
-
- # For generating line shortening candidates.
- SHORTEN_OPERATOR_GROUPS = frozenset([
- frozenset([',']),
- frozenset(['%']),
- frozenset([',', '(', '[', '{']),
- frozenset(['%', '(', '[', '{']),
- frozenset([',', '(', '[', '{', '%', '+', '-', '*', '/', '//']),
- frozenset(['%', '+', '-', '*', '/', '//']),
- ])
-
-
- DEFAULT_IGNORE = 'E226,E24,W50,W690' # TODO: use pycodestyle.DEFAULT_IGNORE
- DEFAULT_INDENT_SIZE = 4
- # these fixes conflict with each other, if the `--ignore` setting causes both
- # to be enabled, disable both of them
- CONFLICTING_CODES = ('W503', 'W504')
-
- # W602 is handled separately due to the need to avoid "with_traceback".
- CODE_TO_2TO3 = {
- 'E231': ['ws_comma'],
- 'E721': ['idioms'],
- 'W690': ['apply',
- 'except',
- 'exitfunc',
- 'numliterals',
- 'operator',
- 'paren',
- 'reduce',
- 'renames',
- 'standarderror',
- 'sys_exc',
- 'throw',
- 'tuple_params',
- 'xreadlines']}
-
-
- if sys.platform == 'win32': # pragma: no cover
- DEFAULT_CONFIG = os.path.expanduser(r'~\.pycodestyle')
- else:
- DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or
- os.path.expanduser('~/.config'),
- 'pycodestyle')
- # fallback, use .pep8
- if not os.path.exists(DEFAULT_CONFIG): # pragma: no cover
- if sys.platform == 'win32':
- DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8')
- else:
- DEFAULT_CONFIG = os.path.join(os.path.expanduser('~/.config'), 'pep8')
- PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8', '.flake8')
-
-
- MAX_PYTHON_FILE_DETECTION_BYTES = 1024
-
-
- def open_with_encoding(filename, mode='r', encoding=None, limit_byte_check=-1):
- """Return opened file with a specific encoding."""
- if not encoding:
- encoding = detect_encoding(filename, limit_byte_check=limit_byte_check)
-
- return io.open(filename, mode=mode, encoding=encoding,
- newline='') # Preserve line endings
-
-
- def detect_encoding(filename, limit_byte_check=-1):
- """Return file encoding."""
- try:
- with open(filename, 'rb') as input_file:
- from lib2to3.pgen2 import tokenize as lib2to3_tokenize
- encoding = lib2to3_tokenize.detect_encoding(input_file.readline)[0]
-
- with open_with_encoding(filename, encoding=encoding) as test_file:
- test_file.read(limit_byte_check)
-
- return encoding
- except (LookupError, SyntaxError, UnicodeDecodeError):
- return 'latin-1'
-
-
- def readlines_from_file(filename):
- """Return contents of file."""
- with open_with_encoding(filename) as input_file:
- return input_file.readlines()
-
-
- def extended_blank_lines(logical_line,
- blank_lines,
- blank_before,
- indent_level,
- previous_logical):
- """Check for missing blank lines after class declaration."""
- if previous_logical.startswith('def '):
- if blank_lines and pycodestyle.DOCSTRING_REGEX.match(logical_line):
- yield (0, 'E303 too many blank lines ({})'.format(blank_lines))
- elif pycodestyle.DOCSTRING_REGEX.match(previous_logical):
- # Missing blank line between class docstring and method declaration.
- if (
- indent_level and
- not blank_lines and
- not blank_before and
- logical_line.startswith(('def ')) and
- '(self' in logical_line
- ):
- yield (0, 'E301 expected 1 blank line, found 0')
-
-
- pycodestyle.register_check(extended_blank_lines)
-
-
- def continued_indentation(logical_line, tokens, indent_level, hang_closing,
- indent_char, noqa):
- """Override pycodestyle's function to provide indentation information."""
- first_row = tokens[0][2][0]
- nrows = 1 + tokens[-1][2][0] - first_row
- if noqa or nrows == 1:
- return
-
- # indent_next tells us whether the next block is indented. Assuming
- # that it is indented by 4 spaces, then we should not allow 4-space
- # indents on the final continuation line. In turn, some other
- # indents are allowed to have an extra 4 spaces.
- indent_next = logical_line.endswith(':')
-
- row = depth = 0
- valid_hangs = (
- (DEFAULT_INDENT_SIZE,)
- if indent_char != '\t' else (DEFAULT_INDENT_SIZE,
- 2 * DEFAULT_INDENT_SIZE)
- )
-
- # Remember how many brackets were opened on each line.
- parens = [0] * nrows
-
- # Relative indents of physical lines.
- rel_indent = [0] * nrows
-
- # For each depth, collect a list of opening rows.
- open_rows = [[0]]
- # For each depth, memorize the hanging indentation.
- hangs = [None]
-
- # Visual indents.
- indent_chances = {}
- last_indent = tokens[0][2]
- indent = [last_indent[1]]
-
- last_token_multiline = None
- line = None
- last_line = ''
- last_line_begins_with_multiline = False
- for token_type, text, start, end, line in tokens:
-
- newline = row < start[0] - first_row
- if newline:
- row = start[0] - first_row
- newline = (not last_token_multiline and
- token_type not in (tokenize.NL, tokenize.NEWLINE))
- last_line_begins_with_multiline = last_token_multiline
-
- if newline:
- # This is the beginning of a continuation line.
- last_indent = start
-
- # Record the initial indent.
- rel_indent[row] = pycodestyle.expand_indent(line) - indent_level
-
- # Identify closing bracket.
- close_bracket = (token_type == tokenize.OP and text in ']})')
-
- # Is the indent relative to an opening bracket line?
- for open_row in reversed(open_rows[depth]):
- hang = rel_indent[row] - rel_indent[open_row]
- hanging_indent = hang in valid_hangs
- if hanging_indent:
- break
- if hangs[depth]:
- hanging_indent = (hang == hangs[depth])
-
- visual_indent = (not close_bracket and hang > 0 and
- indent_chances.get(start[1]))
-
- if close_bracket and indent[depth]:
- # Closing bracket for visual indent.
- if start[1] != indent[depth]:
- yield (start, 'E124 {}'.format(indent[depth]))
- elif close_bracket and not hang:
- # closing bracket matches indentation of opening bracket's line
- if hang_closing:
- yield (start, 'E133 {}'.format(indent[depth]))
- elif indent[depth] and start[1] < indent[depth]:
- if visual_indent is not True:
- # Visual indent is broken.
- yield (start, 'E128 {}'.format(indent[depth]))
- elif (hanging_indent or
- (indent_next and
- rel_indent[row] == 2 * DEFAULT_INDENT_SIZE)):
- # Hanging indent is verified.
- if close_bracket and not hang_closing:
- yield (start, 'E123 {}'.format(indent_level +
- rel_indent[open_row]))
- hangs[depth] = hang
- elif visual_indent is True:
- # Visual indent is verified.
- indent[depth] = start[1]
- elif visual_indent in (text, str):
- # Ignore token lined up with matching one from a previous line.
- pass
- else:
- one_indented = (indent_level + rel_indent[open_row] +
- DEFAULT_INDENT_SIZE)
- # Indent is broken.
- if hang <= 0:
- error = ('E122', one_indented)
- elif indent[depth]:
- error = ('E127', indent[depth])
- elif not close_bracket and hangs[depth]:
- error = ('E131', one_indented)
- elif hang > DEFAULT_INDENT_SIZE:
- error = ('E126', one_indented)
- else:
- hangs[depth] = hang
- error = ('E121', one_indented)
-
- yield (start, '{} {}'.format(*error))
-
- # Look for visual indenting.
- if (
- parens[row] and
- token_type not in (tokenize.NL, tokenize.COMMENT) and
- not indent[depth]
- ):
- indent[depth] = start[1]
- indent_chances[start[1]] = True
- # Deal with implicit string concatenation.
- elif (token_type in (tokenize.STRING, tokenize.COMMENT) or
- text in ('u', 'ur', 'b', 'br')):
- indent_chances[start[1]] = str
- # Special case for the "if" statement because len("if (") is equal to
- # 4.
- elif not indent_chances and not row and not depth and text == 'if':
- indent_chances[end[1] + 1] = True
- elif text == ':' and line[end[1]:].isspace():
- open_rows[depth].append(row)
-
- # Keep track of bracket depth.
- if token_type == tokenize.OP:
- if text in '([{':
- depth += 1
- indent.append(0)
- hangs.append(None)
- if len(open_rows) == depth:
- open_rows.append([])
- open_rows[depth].append(row)
- parens[row] += 1
- elif text in ')]}' and depth > 0:
- # Parent indents should not be more than this one.
- prev_indent = indent.pop() or last_indent[1]
- hangs.pop()
- for d in range(depth):
- if indent[d] > prev_indent:
- indent[d] = 0
- for ind in list(indent_chances):
- if ind >= prev_indent:
- del indent_chances[ind]
- del open_rows[depth + 1:]
- depth -= 1
- if depth:
- indent_chances[indent[depth]] = True
- for idx in range(row, -1, -1):
- if parens[idx]:
- parens[idx] -= 1
- break
- assert len(indent) == depth + 1
- if (
- start[1] not in indent_chances and
- # This is for purposes of speeding up E121 (GitHub #90).
- not last_line.rstrip().endswith(',')
- ):
- # Allow to line up tokens.
- indent_chances[start[1]] = text
-
- last_token_multiline = (start[0] != end[0])
- if last_token_multiline:
- rel_indent[end[0] - first_row] = rel_indent[row]
-
- last_line = line
-
- if (
- indent_next and
- not last_line_begins_with_multiline and
- pycodestyle.expand_indent(line) == indent_level + DEFAULT_INDENT_SIZE
- ):
- pos = (start[0], indent[0] + 4)
- desired_indent = indent_level + 2 * DEFAULT_INDENT_SIZE
- if visual_indent:
- yield (pos, 'E129 {}'.format(desired_indent))
- else:
- yield (pos, 'E125 {}'.format(desired_indent))
-
-
- del pycodestyle._checks['logical_line'][pycodestyle.continued_indentation]
- pycodestyle.register_check(continued_indentation)
-
-
- class FixPEP8(object):
-
- """Fix invalid code.
-
- Fixer methods are prefixed "fix_". The _fix_source() method looks for these
- automatically.
-
- The fixer method can take either one or two arguments (in addition to
- self). The first argument is "result", which is the error information from
- pycodestyle. The second argument, "logical", is required only for
- logical-line fixes.
-
- The fixer method can return the list of modified lines or None. An empty
- list would mean that no changes were made. None would mean that only the
- line reported in the pycodestyle error was modified. Note that the modified
- line numbers that are returned are indexed at 1. This typically would
- correspond with the line number reported in the pycodestyle error
- information.
-
- [fixed method list]
- - e111,e114,e115,e116
- - e121,e122,e123,e124,e125,e126,e127,e128,e129
- - e201,e202,e203
- - e211
- - e221,e222,e223,e224,e225
- - e231
- - e251,e252
- - e261,e262
- - e271,e272,e273,e274,e275
- - e301,e302,e303,e304,e305,e306
- - e401,e402
- - e502
- - e701,e702,e703,e704
- - e711,e712,e713,e714
- - e722
- - e731
- - w291
- - w503,504
-
- """
-
- def __init__(self, filename,
- options,
- contents=None,
- long_line_ignore_cache=None):
- self.filename = filename
- if contents is None:
- self.source = readlines_from_file(filename)
- else:
- sio = io.StringIO(contents)
- self.source = sio.readlines()
- self.options = options
- self.indent_word = _get_indentword(''.join(self.source))
-
- # collect imports line
- self.imports = {}
- for i, line in enumerate(self.source):
- if (line.find("import ") == 0 or line.find("from ") == 0) and \
- line not in self.imports:
- # collect only import statements that first appeared
- self.imports[line] = i
-
- self.long_line_ignore_cache = (
- set() if long_line_ignore_cache is None
- else long_line_ignore_cache)
-
- # Many fixers are the same even though pycodestyle categorizes them
- # differently.
- self.fix_e115 = self.fix_e112
- self.fix_e121 = self._fix_reindent
- self.fix_e122 = self._fix_reindent
- self.fix_e123 = self._fix_reindent
- self.fix_e124 = self._fix_reindent
- self.fix_e126 = self._fix_reindent
- self.fix_e127 = self._fix_reindent
- self.fix_e128 = self._fix_reindent
- self.fix_e129 = self._fix_reindent
- self.fix_e133 = self.fix_e131
- self.fix_e202 = self.fix_e201
- self.fix_e203 = self.fix_e201
- self.fix_e211 = self.fix_e201
- self.fix_e221 = self.fix_e271
- self.fix_e222 = self.fix_e271
- self.fix_e223 = self.fix_e271
- self.fix_e226 = self.fix_e225
- self.fix_e227 = self.fix_e225
- self.fix_e228 = self.fix_e225
- self.fix_e241 = self.fix_e271
- self.fix_e242 = self.fix_e224
- self.fix_e252 = self.fix_e225
- self.fix_e261 = self.fix_e262
- self.fix_e272 = self.fix_e271
- self.fix_e273 = self.fix_e271
- self.fix_e274 = self.fix_e271
- self.fix_e275 = self.fix_e271
- self.fix_e306 = self.fix_e301
- self.fix_e501 = (
- self.fix_long_line_logically if
- options and (options.aggressive >= 2 or options.experimental) else
- self.fix_long_line_physically)
- self.fix_e703 = self.fix_e702
- self.fix_w292 = self.fix_w291
- self.fix_w293 = self.fix_w291
-
- def _fix_source(self, results):
- try:
- (logical_start, logical_end) = _find_logical(self.source)
- logical_support = True
- except (SyntaxError, tokenize.TokenError): # pragma: no cover
- logical_support = False
-
- completed_lines = set()
- for result in sorted(results, key=_priority_key):
- if result['line'] in completed_lines:
- continue
-
- fixed_methodname = 'fix_' + result['id'].lower()
- if hasattr(self, fixed_methodname):
- fix = getattr(self, fixed_methodname)
-
- line_index = result['line'] - 1
- original_line = self.source[line_index]
-
- is_logical_fix = len(_get_parameters(fix)) > 2
- if is_logical_fix:
- logical = None
- if logical_support:
- logical = _get_logical(self.source,
- result,
- logical_start,
- logical_end)
- if logical and set(range(
- logical[0][0] + 1,
- logical[1][0] + 1)).intersection(
- completed_lines):
- continue
-
- modified_lines = fix(result, logical)
- else:
- modified_lines = fix(result)
-
- if modified_lines is None:
- # Force logical fixes to report what they modified.
- assert not is_logical_fix
-
- if self.source[line_index] == original_line:
- modified_lines = []
-
- if modified_lines:
- completed_lines.update(modified_lines)
- elif modified_lines == []: # Empty list means no fix
- if self.options.verbose >= 2:
- print(
- '---> Not fixing {error} on line {line}'.format(
- error=result['id'], line=result['line']),
- file=sys.stderr)
- else: # We assume one-line fix when None.
- completed_lines.add(result['line'])
- else:
- if self.options.verbose >= 3:
- print(
- "---> '{}' is not defined.".format(fixed_methodname),
- file=sys.stderr)
-
- info = result['info'].strip()
- print('---> {}:{}:{}:{}'.format(self.filename,
- result['line'],
- result['column'],
- info),
- file=sys.stderr)
-
- def fix(self):
- """Return a version of the source code with PEP 8 violations fixed."""
- pep8_options = {
- 'ignore': self.options.ignore,
- 'select': self.options.select,
- 'max_line_length': self.options.max_line_length,
- 'hang_closing': self.options.hang_closing,
- }
- results = _execute_pep8(pep8_options, self.source)
-
- if self.options.verbose:
- progress = {}
- for r in results:
- if r['id'] not in progress:
- progress[r['id']] = set()
- progress[r['id']].add(r['line'])
- print('---> {n} issue(s) to fix {progress}'.format(
- n=len(results), progress=progress), file=sys.stderr)
-
- if self.options.line_range:
- start, end = self.options.line_range
- results = [r for r in results
- if start <= r['line'] <= end]
-
- self._fix_source(filter_results(source=''.join(self.source),
- results=results,
- aggressive=self.options.aggressive))
-
- if self.options.line_range:
- # If number of lines has changed then change line_range.
- count = sum(sline.count('\n')
- for sline in self.source[start - 1:end])
- self.options.line_range[1] = start + count - 1
-
- return ''.join(self.source)
-
- def _fix_reindent(self, result):
- """Fix a badly indented line.
-
- This is done by adding or removing from its initial indent only.
-
- """
- num_indent_spaces = int(result['info'].split()[1])
- line_index = result['line'] - 1
- target = self.source[line_index]
-
- self.source[line_index] = ' ' * num_indent_spaces + target.lstrip()
-
- def fix_e112(self, result):
- """Fix under-indented comments."""
- line_index = result['line'] - 1
- target = self.source[line_index]
-
- if not target.lstrip().startswith('#'):
- # Don't screw with invalid syntax.
- return []
-
- self.source[line_index] = self.indent_word + target
-
- def fix_e113(self, result):
- """Fix unexpected indentation."""
- line_index = result['line'] - 1
- target = self.source[line_index]
- indent = _get_indentation(target)
- stripped = target.lstrip()
- self.source[line_index] = indent[1:] + stripped
-
- def fix_e116(self, result):
- """Fix over-indented comments."""
- line_index = result['line'] - 1
- target = self.source[line_index]
-
- indent = _get_indentation(target)
- stripped = target.lstrip()
-
- if not stripped.startswith('#'):
- # Don't screw with invalid syntax.
- return []
-
- self.source[line_index] = indent[1:] + stripped
-
- def fix_e117(self, result):
- """Fix over-indented."""
- line_index = result['line'] - 1
- target = self.source[line_index]
-
- indent = _get_indentation(target)
- if indent == '\t':
- return []
-
- stripped = target.lstrip()
-
- self.source[line_index] = indent[1:] + stripped
-
- def fix_e125(self, result):
- """Fix indentation undistinguish from the next logical line."""
- num_indent_spaces = int(result['info'].split()[1])
- line_index = result['line'] - 1
- target = self.source[line_index]
-
- spaces_to_add = num_indent_spaces - len(_get_indentation(target))
- indent = len(_get_indentation(target))
- modified_lines = []
-
- while len(_get_indentation(self.source[line_index])) >= indent:
- self.source[line_index] = (' ' * spaces_to_add +
- self.source[line_index])
- modified_lines.append(1 + line_index) # Line indexed at 1.
- line_index -= 1
-
- return modified_lines
-
- def fix_e131(self, result):
- """Fix indentation undistinguish from the next logical line."""
- num_indent_spaces = int(result['info'].split()[1])
- line_index = result['line'] - 1
- target = self.source[line_index]
-
- spaces_to_add = num_indent_spaces - len(_get_indentation(target))
-
- indent_length = len(_get_indentation(target))
- spaces_to_add = num_indent_spaces - indent_length
- if num_indent_spaces == 0 and indent_length == 0:
- spaces_to_add = 4
-
- if spaces_to_add >= 0:
- self.source[line_index] = (' ' * spaces_to_add +
- self.source[line_index])
- else:
- offset = abs(spaces_to_add)
- self.source[line_index] = self.source[line_index][offset:]
-
- def fix_e201(self, result):
- """Remove extraneous whitespace."""
- line_index = result['line'] - 1
- target = self.source[line_index]
- offset = result['column'] - 1
-
- fixed = fix_whitespace(target,
- offset=offset,
- replacement='')
-
- self.source[line_index] = fixed
-
- def fix_e224(self, result):
- """Remove extraneous whitespace around operator."""
- target = self.source[result['line'] - 1]
- offset = result['column'] - 1
- fixed = target[:offset] + target[offset:].replace('\t', ' ')
- self.source[result['line'] - 1] = fixed
-
- def fix_e225(self, result):
- """Fix missing whitespace around operator."""
- target = self.source[result['line'] - 1]
- offset = result['column'] - 1
- fixed = target[:offset] + ' ' + target[offset:]
-
- # Only proceed if non-whitespace characters match.
- # And make sure we don't break the indentation.
- if (
- fixed.replace(' ', '') == target.replace(' ', '') and
- _get_indentation(fixed) == _get_indentation(target)
- ):
- self.source[result['line'] - 1] = fixed
- error_code = result.get('id', 0)
- try:
- ts = generate_tokens(fixed)
- except (SyntaxError, tokenize.TokenError):
- return
- if not check_syntax(fixed.lstrip()):
- return
- errors = list(
- pycodestyle.missing_whitespace_around_operator(fixed, ts))
- for e in reversed(errors):
- if error_code != e[1].split()[0]:
- continue
- offset = e[0][1]
- fixed = fixed[:offset] + ' ' + fixed[offset:]
- self.source[result['line'] - 1] = fixed
- else:
- return []
-
- def fix_e231(self, result):
- """Add missing whitespace."""
- line_index = result['line'] - 1
- target = self.source[line_index]
- offset = result['column']
- fixed = target[:offset].rstrip() + ' ' + target[offset:].lstrip()
- self.source[line_index] = fixed
-
- def fix_e251(self, result):
- """Remove whitespace around parameter '=' sign."""
- line_index = result['line'] - 1
- target = self.source[line_index]
-
- # This is necessary since pycodestyle sometimes reports columns that
- # goes past the end of the physical line. This happens in cases like,
- # foo(bar\n=None)
- c = min(result['column'] - 1,
- len(target) - 1)
-
- if target[c].strip():
- fixed = target
- else:
- fixed = target[:c].rstrip() + target[c:].lstrip()
-
- # There could be an escaped newline
- #
- # def foo(a=\
- # 1)
- if fixed.endswith(('=\\\n', '=\\\r\n', '=\\\r')):
- self.source[line_index] = fixed.rstrip('\n\r \t\\')
- self.source[line_index + 1] = self.source[line_index + 1].lstrip()
- return [line_index + 1, line_index + 2] # Line indexed at 1
-
- self.source[result['line'] - 1] = fixed
-
- def fix_e262(self, result):
- """Fix spacing after inline comment hash."""
- target = self.source[result['line'] - 1]
- offset = result['column']
-
- code = target[:offset].rstrip(' \t#')
- comment = target[offset:].lstrip(' \t#')
-
- fixed = code + (' # ' + comment if comment.strip() else '\n')
-
- self.source[result['line'] - 1] = fixed
-
- def fix_e265(self, result):
- """Fix spacing after block comment hash."""
- target = self.source[result['line'] - 1]
-
- indent = _get_indentation(target)
- line = target.lstrip(' \t')
- pos = next((index for index, c in enumerate(line) if c != '#'))
- hashes = line[:pos]
- comment = line[pos:].lstrip(' \t')
-
- # Ignore special comments, even in the middle of the file.
- if comment.startswith('!'):
- return
-
- fixed = indent + hashes + (' ' + comment if comment.strip() else '\n')
-
- self.source[result['line'] - 1] = fixed
-
- def fix_e266(self, result):
- """Fix too many block comment hashes."""
- target = self.source[result['line'] - 1]
-
- # Leave stylistic outlined blocks alone.
- if target.strip().endswith('#'):
- return
-
- indentation = _get_indentation(target)
- fixed = indentation + '# ' + target.lstrip('# \t')
-
- self.source[result['line'] - 1] = fixed
-
- def fix_e271(self, result):
- """Fix extraneous whitespace around keywords."""
- line_index = result['line'] - 1
- target = self.source[line_index]
- offset = result['column'] - 1
-
- fixed = fix_whitespace(target,
- offset=offset,
- replacement=' ')
-
- if fixed == target:
- return []
- else:
- self.source[line_index] = fixed
-
- def fix_e301(self, result):
- """Add missing blank line."""
- cr = '\n'
- self.source[result['line'] - 1] = cr + self.source[result['line'] - 1]
-
- def fix_e302(self, result):
- """Add missing 2 blank lines."""
- add_linenum = 2 - int(result['info'].split()[-1])
- offset = 1
- if self.source[result['line'] - 2].strip() == "\\":
- offset = 2
- cr = '\n' * add_linenum
- self.source[result['line'] - offset] = (
- cr + self.source[result['line'] - offset]
- )
-
- def fix_e303(self, result):
- """Remove extra blank lines."""
- delete_linenum = int(result['info'].split('(')[1].split(')')[0]) - 2
- delete_linenum = max(1, delete_linenum)
-
- # We need to count because pycodestyle reports an offset line number if
- # there are comments.
- cnt = 0
- line = result['line'] - 2
- modified_lines = []
- while cnt < delete_linenum and line >= 0:
- if not self.source[line].strip():
- self.source[line] = ''
- modified_lines.append(1 + line) # Line indexed at 1
- cnt += 1
- line -= 1
-
- return modified_lines
-
- def fix_e304(self, result):
- """Remove blank line following function decorator."""
- line = result['line'] - 2
- if not self.source[line].strip():
- self.source[line] = ''
-
- def fix_e305(self, result):
- """Add missing 2 blank lines after end of function or class."""
- add_delete_linenum = 2 - int(result['info'].split()[-1])
- cnt = 0
- offset = result['line'] - 2
- modified_lines = []
- if add_delete_linenum < 0:
- # delete cr
- add_delete_linenum = abs(add_delete_linenum)
- while cnt < add_delete_linenum and offset >= 0:
- if not self.source[offset].strip():
- self.source[offset] = ''
- modified_lines.append(1 + offset) # Line indexed at 1
- cnt += 1
- offset -= 1
- else:
- # add cr
- cr = '\n'
- # check comment line
- while True:
- if offset < 0:
- break
- line = self.source[offset].lstrip()
- if not line:
- break
- if line[0] != '#':
- break
- offset -= 1
- offset += 1
- self.source[offset] = cr + self.source[offset]
- modified_lines.append(1 + offset) # Line indexed at 1.
- return modified_lines
-
- def fix_e401(self, result):
- """Put imports on separate lines."""
- line_index = result['line'] - 1
- target = self.source[line_index]
- offset = result['column'] - 1
-
- if not target.lstrip().startswith('import'):
- return []
-
- indentation = re.split(pattern=r'\bimport\b',
- string=target, maxsplit=1)[0]
- fixed = (target[:offset].rstrip('\t ,') + '\n' +
- indentation + 'import ' + target[offset:].lstrip('\t ,'))
- self.source[line_index] = fixed
-
- def fix_e402(self, result):
- (line_index, offset, target) = get_index_offset_contents(result,
- self.source)
- for i in range(1, 100):
- line = "".join(self.source[line_index:line_index+i])
- try:
- generate_tokens("".join(line))
- except (SyntaxError, tokenize.TokenError):
- continue
- break
- if not (target in self.imports and self.imports[target] != line_index):
- mod_offset = get_module_imports_on_top_of_file(self.source,
- line_index)
- self.source[mod_offset] = line + self.source[mod_offset]
- for offset in range(i):
- self.source[line_index+offset] = ''
-
- def fix_long_line_logically(self, result, logical):
- """Try to make lines fit within --max-line-length characters."""
- if (
- not logical or
- len(logical[2]) == 1 or
- self.source[result['line'] - 1].lstrip().startswith('#')
- ):
- return self.fix_long_line_physically(result)
-
- start_line_index = logical[0][0]
- end_line_index = logical[1][0]
- logical_lines = logical[2]
-
- previous_line = get_item(self.source, start_line_index - 1, default='')
- next_line = get_item(self.source, end_line_index + 1, default='')
-
- single_line = join_logical_line(''.join(logical_lines))
-
- try:
- fixed = self.fix_long_line(
- target=single_line,
- previous_line=previous_line,
- next_line=next_line,
- original=''.join(logical_lines))
- except (SyntaxError, tokenize.TokenError):
- return self.fix_long_line_physically(result)
-
- if fixed:
- for line_index in range(start_line_index, end_line_index + 1):
- self.source[line_index] = ''
- self.source[start_line_index] = fixed
- return range(start_line_index + 1, end_line_index + 1)
-
- return []
-
- def fix_long_line_physically(self, result):
- """Try to make lines fit within --max-line-length characters."""
- line_index = result['line'] - 1
- target = self.source[line_index]
-
- previous_line = get_item(self.source, line_index - 1, default='')
- next_line = get_item(self.source, line_index + 1, default='')
-
- try:
- fixed = self.fix_long_line(
- target=target,
- previous_line=previous_line,
- next_line=next_line,
- original=target)
- except (SyntaxError, tokenize.TokenError):
- return []
-
- if fixed:
- self.source[line_index] = fixed
- return [line_index + 1]
-
- return []
-
- def fix_long_line(self, target, previous_line,
- next_line, original):
- cache_entry = (target, previous_line, next_line)
- if cache_entry in self.long_line_ignore_cache:
- return []
-
- if target.lstrip().startswith('#'):
- if self.options.aggressive:
- # Wrap commented lines.
- return shorten_comment(
- line=target,
- max_line_length=self.options.max_line_length,
- last_comment=not next_line.lstrip().startswith('#'))
- return []
-
- fixed = get_fixed_long_line(
- target=target,
- previous_line=previous_line,
- original=original,
- indent_word=self.indent_word,
- max_line_length=self.options.max_line_length,
- aggressive=self.options.aggressive,
- experimental=self.options.experimental,
- verbose=self.options.verbose)
-
- if fixed and not code_almost_equal(original, fixed):
- return fixed
-
- self.long_line_ignore_cache.add(cache_entry)
- return None
-
- def fix_e502(self, result):
- """Remove extraneous escape of newline."""
- (line_index, _, target) = get_index_offset_contents(result,
- self.source)
- self.source[line_index] = target.rstrip('\n\r \t\\') + '\n'
-
- def fix_e701(self, result):
- """Put colon-separated compound statement on separate lines."""
- line_index = result['line'] - 1
- target = self.source[line_index]
- c = result['column']
-
- fixed_source = (target[:c] + '\n' +
- _get_indentation(target) + self.indent_word +
- target[c:].lstrip('\n\r \t\\'))
- self.source[result['line'] - 1] = fixed_source
- return [result['line'], result['line'] + 1]
-
- def fix_e702(self, result, logical):
- """Put semicolon-separated compound statement on separate lines."""
- if not logical:
- return [] # pragma: no cover
- logical_lines = logical[2]
-
- # Avoid applying this when indented.
- # https://docs.python.org/reference/compound_stmts.html
- for line in logical_lines:
- if (result['id'] == 'E702' and ':' in line
- and STARTSWITH_INDENT_STATEMENT_REGEX.match(line)):
- if self.options.verbose:
- print(
- '---> avoid fixing {error} with '
- 'other compound statements'.format(error=result['id']),
- file=sys.stderr
- )
- return []
-
- line_index = result['line'] - 1
- target = self.source[line_index]
-
- if target.rstrip().endswith('\\'):
- # Normalize '1; \\\n2' into '1; 2'.
- self.source[line_index] = target.rstrip('\n \r\t\\')
- self.source[line_index + 1] = self.source[line_index + 1].lstrip()
- return [line_index + 1, line_index + 2]
-
- if target.rstrip().endswith(';'):
- self.source[line_index] = target.rstrip('\n \r\t;') + '\n'
- return [line_index + 1]
-
- offset = result['column'] - 1
- first = target[:offset].rstrip(';').rstrip()
- second = (_get_indentation(logical_lines[0]) +
- target[offset:].lstrip(';').lstrip())
-
- # Find inline comment.
- inline_comment = None
- if target[offset:].lstrip(';').lstrip()[:2] == '# ':
- inline_comment = target[offset:].lstrip(';')
-
- if inline_comment:
- self.source[line_index] = first + inline_comment
- else:
- self.source[line_index] = first + '\n' + second
- return [line_index + 1]
-
- def fix_e704(self, result):
- """Fix multiple statements on one line def"""
- (line_index, _, target) = get_index_offset_contents(result,
- self.source)
- match = STARTSWITH_DEF_REGEX.match(target)
- if match:
- self.source[line_index] = '{}\n{}{}'.format(
- match.group(0),
- _get_indentation(target) + self.indent_word,
- target[match.end(0):].lstrip())
-
- def fix_e711(self, result):
- """Fix comparison with None."""
- (line_index, offset, target) = get_index_offset_contents(result,
- self.source)
-
- right_offset = offset + 2
- if right_offset >= len(target):
- return []
-
- left = target[:offset].rstrip()
- center = target[offset:right_offset]
- right = target[right_offset:].lstrip()
-
- if center.strip() == '==':
- new_center = 'is'
- elif center.strip() == '!=':
- new_center = 'is not'
- else:
- return []
-
- self.source[line_index] = ' '.join([left, new_center, right])
-
- def fix_e712(self, result):
- """Fix (trivial case of) comparison with boolean."""
- (line_index, offset, target) = get_index_offset_contents(result,
- self.source)
-
- # Handle very easy "not" special cases.
- if re.match(r'^\s*if [\w."\'\[\]]+ == False:$', target):
- self.source[line_index] = re.sub(r'if ([\w."\'\[\]]+) == False:',
- r'if not \1:', target, count=1)
- elif re.match(r'^\s*if [\w."\'\[\]]+ != True:$', target):
- self.source[line_index] = re.sub(r'if ([\w."\'\[\]]+) != True:',
- r'if not \1:', target, count=1)
- else:
- right_offset = offset + 2
- if right_offset >= len(target):
- return []
-
- left = target[:offset].rstrip()
- center = target[offset:right_offset]
- right = target[right_offset:].lstrip()
-
- # Handle simple cases only.
- new_right = None
- if center.strip() == '==':
- if re.match(r'\bTrue\b', right):
- new_right = re.sub(r'\bTrue\b *', '', right, count=1)
- elif center.strip() == '!=':
- if re.match(r'\bFalse\b', right):
- new_right = re.sub(r'\bFalse\b *', '', right, count=1)
-
- if new_right is None:
- return []
-
- if new_right[0].isalnum():
- new_right = ' ' + new_right
-
- self.source[line_index] = left + new_right
-
- def fix_e713(self, result):
- """Fix (trivial case of) non-membership check."""
- (line_index, offset, target) = get_index_offset_contents(result,
- self.source)
-
- # to convert once 'not in' -> 'in'
- before_target = target[:offset]
- target = target[offset:]
- match_notin = COMPARE_NEGATIVE_REGEX_THROUGH.search(target)
- notin_pos_start, notin_pos_end = 0, 0
- if match_notin:
- notin_pos_start = match_notin.start(1)
- notin_pos_end = match_notin.end()
- target = '{}{} {}'.format(
- target[:notin_pos_start], 'in', target[notin_pos_end:])
-
- # fix 'not in'
- match = COMPARE_NEGATIVE_REGEX.search(target)
- if match:
- if match.group(3) == 'in':
- pos_start = match.start(1)
- new_target = '{5}{0}{1} {2} {3} {4}'.format(
- target[:pos_start], match.group(2), match.group(1),
- match.group(3), target[match.end():], before_target)
- if match_notin:
- # revert 'in' -> 'not in'
- pos_start = notin_pos_start + offset
- pos_end = notin_pos_end + offset - 4 # len('not ')
- new_target = '{}{} {}'.format(
- new_target[:pos_start], 'not in', new_target[pos_end:])
- self.source[line_index] = new_target
-
- def fix_e714(self, result):
- """Fix object identity should be 'is not' case."""
- (line_index, offset, target) = get_index_offset_contents(result,
- self.source)
-
- # to convert once 'is not' -> 'is'
- before_target = target[:offset]
- target = target[offset:]
- match_isnot = COMPARE_NEGATIVE_REGEX_THROUGH.search(target)
- isnot_pos_start, isnot_pos_end = 0, 0
- if match_isnot:
- isnot_pos_start = match_isnot.start(1)
- isnot_pos_end = match_isnot.end()
- target = '{}{} {}'.format(
- target[:isnot_pos_start], 'in', target[isnot_pos_end:])
-
- match = COMPARE_NEGATIVE_REGEX.search(target)
- if match:
- if match.group(3).startswith('is'):
- pos_start = match.start(1)
- new_target = '{5}{0}{1} {2} {3} {4}'.format(
- target[:pos_start], match.group(2), match.group(3),
- match.group(1), target[match.end():], before_target)
- if match_isnot:
- # revert 'is' -> 'is not'
- pos_start = isnot_pos_start + offset
- pos_end = isnot_pos_end + offset - 4 # len('not ')
- new_target = '{}{} {}'.format(
- new_target[:pos_start], 'is not', new_target[pos_end:])
- self.source[line_index] = new_target
-
- def fix_e722(self, result):
- """fix bare except"""
- (line_index, _, target) = get_index_offset_contents(result,
- self.source)
- match = BARE_EXCEPT_REGEX.search(target)
- if match:
- self.source[line_index] = '{}{}{}'.format(
- target[:result['column'] - 1], "except BaseException:",
- target[match.end():])
-
- def fix_e731(self, result):
- """Fix do not assign a lambda expression check."""
- (line_index, _, target) = get_index_offset_contents(result,
- self.source)
- match = LAMBDA_REGEX.search(target)
- if match:
- end = match.end()
- self.source[line_index] = '{}def {}({}): return {}'.format(
- target[:match.start(0)], match.group(1), match.group(2),
- target[end:].lstrip())
-
- def fix_w291(self, result):
- """Remove trailing whitespace."""
- fixed_line = self.source[result['line'] - 1].rstrip()
- self.source[result['line'] - 1] = fixed_line + '\n'
-
- def fix_w391(self, _):
- """Remove trailing blank lines."""
- blank_count = 0
- for line in reversed(self.source):
- line = line.rstrip()
- if line:
- break
- else:
- blank_count += 1
-
- original_length = len(self.source)
- self.source = self.source[:original_length - blank_count]
- return range(1, 1 + original_length)
-
- def fix_w503(self, result):
- (line_index, _, target) = get_index_offset_contents(result,
- self.source)
- one_string_token = target.split()[0]
- try:
- ts = generate_tokens(one_string_token)
- except (SyntaxError, tokenize.TokenError):
- return
- if not _is_binary_operator(ts[0][0], one_string_token):
- return
- # find comment
- comment_index = 0
- found_not_comment_only_line = False
- comment_only_linenum = 0
- for i in range(5):
- # NOTE: try to parse code in 5 times
- if (line_index - i) < 0:
- break
- from_index = line_index - i - 1
- if from_index < 0 or len(self.source) <= from_index:
- break
- to_index = line_index + 1
- strip_line = self.source[from_index].lstrip()
- if (
- not found_not_comment_only_line and
- strip_line and strip_line[0] == '#'
- ):
- comment_only_linenum += 1
- continue
- found_not_comment_only_line = True
- try:
- ts = generate_tokens("".join(self.source[from_index:to_index]))
- except (SyntaxError, tokenize.TokenError):
- continue
- newline_count = 0
- newline_index = []
- for index, t in enumerate(ts):
- if t[0] in (tokenize.NEWLINE, tokenize.NL):
- newline_index.append(index)
- newline_count += 1
- if newline_count > 2:
- tts = ts[newline_index[-3]:]
- else:
- tts = ts
- old = []
- for t in tts:
- if t[0] in (tokenize.NEWLINE, tokenize.NL):
- newline_count -= 1
- if newline_count <= 1:
- break
- if tokenize.COMMENT == t[0] and old and old[0] != tokenize.NL:
- comment_index = old[3][1]
- break
- old = t
- break
- i = target.index(one_string_token)
- fix_target_line = line_index - 1 - comment_only_linenum
- self.source[line_index] = '{}{}'.format(
- target[:i], target[i + len(one_string_token):].lstrip())
- nl = find_newline(self.source[fix_target_line:line_index])
- before_line = self.source[fix_target_line]
- bl = before_line.index(nl)
- if comment_index:
- self.source[fix_target_line] = '{} {} {}'.format(
- before_line[:comment_index], one_string_token,
- before_line[comment_index + 1:])
- else:
- if before_line[:bl].endswith("#"):
- # special case
- # see: https://github.com/hhatto/autopep8/issues/503
- self.source[fix_target_line] = '{}{} {}'.format(
- before_line[:bl-2], one_string_token, before_line[bl-2:])
- else:
- self.source[fix_target_line] = '{} {}{}'.format(
- before_line[:bl], one_string_token, before_line[bl:])
-
- def fix_w504(self, result):
- (line_index, _, target) = get_index_offset_contents(result,
- self.source)
- # NOTE: is not collect pointed out in pycodestyle==2.4.0
- comment_index = 0
- operator_position = None # (start_position, end_position)
- for i in range(1, 6):
- to_index = line_index + i
- try:
- ts = generate_tokens("".join(self.source[line_index:to_index]))
- except (SyntaxError, tokenize.TokenError):
- continue
- newline_count = 0
- newline_index = []
- for index, t in enumerate(ts):
- if _is_binary_operator(t[0], t[1]):
- if t[2][0] == 1 and t[3][0] == 1:
- operator_position = (t[2][1], t[3][1])
- elif t[0] == tokenize.NAME and t[1] in ("and", "or"):
- if t[2][0] == 1 and t[3][0] == 1:
- operator_position = (t[2][1], t[3][1])
- elif t[0] in (tokenize.NEWLINE, tokenize.NL):
- newline_index.append(index)
- newline_count += 1
- if newline_count > 2:
- tts = ts[:newline_index[-3]]
- else:
- tts = ts
- old = []
- for t in tts:
- if tokenize.COMMENT == t[0] and old:
- comment_row, comment_index = old[3]
- break
- old = t
- break
- if not operator_position:
- return
- target_operator = target[operator_position[0]:operator_position[1]]
-
- if comment_index and comment_row == 1:
- self.source[line_index] = '{}{}'.format(
- target[:operator_position[0]].rstrip(),
- target[comment_index:])
- else:
- self.source[line_index] = '{}{}{}'.format(
- target[:operator_position[0]].rstrip(),
- target[operator_position[1]:].lstrip(),
- target[operator_position[1]:])
-
- next_line = self.source[line_index + 1]
- next_line_indent = 0
- m = re.match(r'\s*', next_line)
- if m:
- next_line_indent = m.span()[1]
- self.source[line_index + 1] = '{}{} {}'.format(
- next_line[:next_line_indent], target_operator,
- next_line[next_line_indent:])
-
- def fix_w605(self, result):
- (line_index, offset, target) = get_index_offset_contents(result,
- self.source)
- self.source[line_index] = '{}\\{}'.format(
- target[:offset + 1], target[offset + 1:])
-
-
- def get_module_imports_on_top_of_file(source, import_line_index):
- """return import or from keyword position
-
- example:
- > 0: import sys
- 1: import os
- 2:
- 3: def function():
- """
- def is_string_literal(line):
- if line[0] in 'uUbB':
- line = line[1:]
- if line and line[0] in 'rR':
- line = line[1:]
- return line and (line[0] == '"' or line[0] == "'")
-
- def is_future_import(line):
- nodes = ast.parse(line)
- for n in nodes.body:
- if isinstance(n, ast.ImportFrom) and n.module == '__future__':
- return True
- return False
-
- def has_future_import(source):
- offset = 0
- line = ''
- for _, next_line in source:
- for line_part in next_line.strip().splitlines(True):
- line = line + line_part
- try:
- return is_future_import(line), offset
- except SyntaxError:
- continue
- offset += 1
- return False, offset
-
- allowed_try_keywords = ('try', 'except', 'else', 'finally')
- in_docstring = False
- docstring_kind = '"""'
- source_stream = iter(enumerate(source))
- for cnt, line in source_stream:
- if not in_docstring:
- m = DOCSTRING_START_REGEX.match(line.lstrip())
- if m is not None:
- in_docstring = True
- docstring_kind = m.group('kind')
- remain = line[m.end(): m.endpos].rstrip()
- if remain[-3:] == docstring_kind: # one line doc
- in_docstring = False
- continue
- if in_docstring:
- if line.rstrip()[-3:] == docstring_kind:
- in_docstring = False
- continue
-
- if not line.rstrip():
- continue
- elif line.startswith('#'):
- continue
-
- if line.startswith('import '):
- if cnt == import_line_index:
- continue
- return cnt
- elif line.startswith('from '):
- if cnt == import_line_index:
- continue
- hit, offset = has_future_import(
- itertools.chain([(cnt, line)], source_stream)
- )
- if hit:
- # move to the back
- return cnt + offset + 1
- return cnt
- elif pycodestyle.DUNDER_REGEX.match(line):
- return cnt
- elif any(line.startswith(kw) for kw in allowed_try_keywords):
- continue
- elif is_string_literal(line):
- return cnt
- else:
- return cnt
- return 0
-
-
- def get_index_offset_contents(result, source):
- """Return (line_index, column_offset, line_contents)."""
- line_index = result['line'] - 1
- return (line_index,
- result['column'] - 1,
- source[line_index])
-
-
- def get_fixed_long_line(target, previous_line, original,
- indent_word=' ', max_line_length=79,
- aggressive=False, experimental=False, verbose=False):
- """Break up long line and return result.
-
- Do this by generating multiple reformatted candidates and then
- ranking the candidates to heuristically select the best option.
-
- """
- indent = _get_indentation(target)
- source = target[len(indent):]
- assert source.lstrip() == source
- assert not target.lstrip().startswith('#')
-
- # Check for partial multiline.
- tokens = list(generate_tokens(source))
-
- candidates = shorten_line(
- tokens, source, indent,
- indent_word,
- max_line_length,
- aggressive=aggressive,
- experimental=experimental,
- previous_line=previous_line)
-
- # Also sort alphabetically as a tie breaker (for determinism).
- candidates = sorted(
- sorted(set(candidates).union([target, original])),
- key=lambda x: line_shortening_rank(
- x,
- indent_word,
- max_line_length,
- experimental=experimental))
-
- if verbose >= 4:
- print(('-' * 79 + '\n').join([''] + candidates + ['']),
- file=wrap_output(sys.stderr, 'utf-8'))
-
- if candidates:
- best_candidate = candidates[0]
-
- # Don't allow things to get longer.
- if longest_line_length(best_candidate) > longest_line_length(original):
- return None
-
- return best_candidate
-
-
- def longest_line_length(code):
- """Return length of longest line."""
- if len(code) == 0:
- return 0
- return max(len(line) for line in code.splitlines())
-
-
- def join_logical_line(logical_line):
- """Return single line based on logical line input."""
- indentation = _get_indentation(logical_line)
-
- return indentation + untokenize_without_newlines(
- generate_tokens(logical_line.lstrip())) + '\n'
-
-
- def untokenize_without_newlines(tokens):
- """Return source code based on tokens."""
- text = ''
- last_row = 0
- last_column = -1
-
- for t in tokens:
- token_string = t[1]
- (start_row, start_column) = t[2]
- (end_row, end_column) = t[3]
-
- if start_row > last_row:
- last_column = 0
- if (
- (start_column > last_column or token_string == '\n') and
- not text.endswith(' ')
- ):
- text += ' '
-
- if token_string != '\n':
- text += token_string
-
- last_row = end_row
- last_column = end_column
-
- return text.rstrip()
-
-
- def _find_logical(source_lines):
- # Make a variable which is the index of all the starts of lines.
- logical_start = []
- logical_end = []
- last_newline = True
- parens = 0
- for t in generate_tokens(''.join(source_lines)):
- if t[0] in [tokenize.COMMENT, tokenize.DEDENT,
- tokenize.INDENT, tokenize.NL,
- tokenize.ENDMARKER]:
- continue
- if not parens and t[0] in [tokenize.NEWLINE, tokenize.SEMI]:
- last_newline = True
- logical_end.append((t[3][0] - 1, t[2][1]))
- continue
- if last_newline and not parens:
- logical_start.append((t[2][0] - 1, t[2][1]))
- last_newline = False
- if t[0] == tokenize.OP:
- if t[1] in '([{':
- parens += 1
- elif t[1] in '}])':
- parens -= 1
- return (logical_start, logical_end)
-
-
- def _get_logical(source_lines, result, logical_start, logical_end):
- """Return the logical line corresponding to the result.
-
- Assumes input is already E702-clean.
-
- """
- row = result['line'] - 1
- col = result['column'] - 1
- ls = None
- le = None
- for i in range(0, len(logical_start), 1):
- assert logical_end
- x = logical_end[i]
- if x[0] > row or (x[0] == row and x[1] > col):
- le = x
- ls = logical_start[i]
- break
- if ls is None:
- return None
- original = source_lines[ls[0]:le[0] + 1]
- return ls, le, original
-
-
- def get_item(items, index, default=None):
- if 0 <= index < len(items):
- return items[index]
-
- return default
-
-
- def reindent(source, indent_size, leave_tabs=False):
- """Reindent all lines."""
- reindenter = Reindenter(source, leave_tabs)
- return reindenter.run(indent_size)
-
-
- def code_almost_equal(a, b):
- """Return True if code is similar.
-
- Ignore whitespace when comparing specific line.
-
- """
- split_a = split_and_strip_non_empty_lines(a)
- split_b = split_and_strip_non_empty_lines(b)
-
- if len(split_a) != len(split_b):
- return False
-
- for (index, _) in enumerate(split_a):
- if ''.join(split_a[index].split()) != ''.join(split_b[index].split()):
- return False
-
- return True
-
-
- def split_and_strip_non_empty_lines(text):
- """Return lines split by newline.
-
- Ignore empty lines.
-
- """
- return [line.strip() for line in text.splitlines() if line.strip()]
-
-
- def refactor(source, fixer_names, ignore=None, filename=''):
- """Return refactored code using lib2to3.
-
- Skip if ignore string is produced in the refactored code.
-
- """
- not_found_end_of_file_newline = source and source.rstrip("\r\n") == source
- if not_found_end_of_file_newline:
- input_source = source + "\n"
- else:
- input_source = source
-
- from lib2to3 import pgen2
- try:
- new_text = refactor_with_2to3(input_source,
- fixer_names=fixer_names,
- filename=filename)
- except (pgen2.parse.ParseError,
- SyntaxError,
- UnicodeDecodeError,
- UnicodeEncodeError):
- return source
-
- if ignore:
- if ignore in new_text and ignore not in source:
- return source
-
- if not_found_end_of_file_newline:
- return new_text.rstrip("\r\n")
-
- return new_text
-
-
- def code_to_2to3(select, ignore, where='', verbose=False):
- fixes = set()
- for code, fix in CODE_TO_2TO3.items():
- if code_match(code, select=select, ignore=ignore):
- if verbose:
- print('---> Applying {} fix for {}'.format(where,
- code.upper()),
- file=sys.stderr)
- fixes |= set(fix)
- return fixes
-
-
- def fix_2to3(source,
- aggressive=True, select=None, ignore=None, filename='',
- where='global', verbose=False):
- """Fix various deprecated code (via lib2to3)."""
- if not aggressive:
- return source
-
- select = select or []
- ignore = ignore or []
-
- return refactor(source,
- code_to_2to3(select=select,
- ignore=ignore,
- where=where,
- verbose=verbose),
- filename=filename)
-
-
- def find_newline(source):
- """Return type of newline used in source.
-
- Input is a list of lines.
-
- """
- assert not isinstance(source, str)
-
- counter = collections.defaultdict(int)
- for line in source:
- if line.endswith(CRLF):
- counter[CRLF] += 1
- elif line.endswith(CR):
- counter[CR] += 1
- elif line.endswith(LF):
- counter[LF] += 1
-
- return (sorted(counter, key=counter.get, reverse=True) or [LF])[0]
-
-
- def _get_indentword(source):
- """Return indentation type."""
- indent_word = ' ' # Default in case source has no indentation
- try:
- for t in generate_tokens(source):
- if t[0] == token.INDENT:
- indent_word = t[1]
- break
- except (SyntaxError, tokenize.TokenError):
- pass
- return indent_word
-
-
- def _get_indentation(line):
- """Return leading whitespace."""
- if line.strip():
- non_whitespace_index = len(line) - len(line.lstrip())
- return line[:non_whitespace_index]
-
- return ''
-
-
- def get_diff_text(old, new, filename):
- """Return text of unified diff between old and new."""
- newline = '\n'
- diff = difflib.unified_diff(
- old, new,
- 'original/' + filename,
- 'fixed/' + filename,
- lineterm=newline)
-
- text = ''
- for line in diff:
- text += line
-
- # Work around missing newline (http://bugs.python.org/issue2142).
- if text and not line.endswith(newline):
- text += newline + r'\ No newline at end of file' + newline
-
- return text
-
-
- def _priority_key(pep8_result):
- """Key for sorting PEP8 results.
-
- Global fixes should be done first. This is important for things like
- indentation.
-
- """
- priority = [
- # Fix multiline colon-based before semicolon based.
- 'e701',
- # Break multiline statements early.
- 'e702',
- # Things that make lines longer.
- 'e225', 'e231',
- # Remove extraneous whitespace before breaking lines.
- 'e201',
- # Shorten whitespace in comment before resorting to wrapping.
- 'e262'
- ]
- middle_index = 10000
- lowest_priority = [
- # We need to shorten lines last since the logical fixer can get in a
- # loop, which causes us to exit early.
- 'e501',
- ]
- key = pep8_result['id'].lower()
- try:
- return priority.index(key)
- except ValueError:
- try:
- return middle_index + lowest_priority.index(key) + 1
- except ValueError:
- return middle_index
-
-
- def shorten_line(tokens, source, indentation, indent_word, max_line_length,
- aggressive=False, experimental=False, previous_line=''):
- """Separate line at OPERATOR.
-
- Multiple candidates will be yielded.
-
- """
- for candidate in _shorten_line(tokens=tokens,
- source=source,
- indentation=indentation,
- indent_word=indent_word,
- aggressive=aggressive,
- previous_line=previous_line):
- yield candidate
-
- if aggressive:
- for key_token_strings in SHORTEN_OPERATOR_GROUPS:
- shortened = _shorten_line_at_tokens(
- tokens=tokens,
- source=source,
- indentation=indentation,
- indent_word=indent_word,
- key_token_strings=key_token_strings,
- aggressive=aggressive)
-
- if shortened is not None and shortened != source:
- yield shortened
-
- if experimental:
- for shortened in _shorten_line_at_tokens_new(
- tokens=tokens,
- source=source,
- indentation=indentation,
- max_line_length=max_line_length):
-
- yield shortened
-
-
- def _shorten_line(tokens, source, indentation, indent_word,
- aggressive=False, previous_line=''):
- """Separate line at OPERATOR.
-
- The input is expected to be free of newlines except for inside multiline
- strings and at the end.
-
- Multiple candidates will be yielded.
-
- """
- for (token_type,
- token_string,
- start_offset,
- end_offset) in token_offsets(tokens):
-
- if (
- token_type == tokenize.COMMENT and
- not is_probably_part_of_multiline(previous_line) and
- not is_probably_part_of_multiline(source) and
- not source[start_offset + 1:].strip().lower().startswith(
- ('noqa', 'pragma:', 'pylint:'))
- ):
- # Move inline comments to previous line.
- first = source[:start_offset]
- second = source[start_offset:]
- yield (indentation + second.strip() + '\n' +
- indentation + first.strip() + '\n')
- elif token_type == token.OP and token_string != '=':
- # Don't break on '=' after keyword as this violates PEP 8.
-
- assert token_type != token.INDENT
-
- first = source[:end_offset]
-
- second_indent = indentation
- if (first.rstrip().endswith('(') and
- source[end_offset:].lstrip().startswith(')')):
- pass
- elif first.rstrip().endswith('('):
- second_indent += indent_word
- elif '(' in first:
- second_indent += ' ' * (1 + first.find('('))
- else:
- second_indent += indent_word
-
- second = (second_indent + source[end_offset:].lstrip())
- if (
- not second.strip() or
- second.lstrip().startswith('#')
- ):
- continue
-
- # Do not begin a line with a comma
- if second.lstrip().startswith(','):
- continue
- # Do end a line with a dot
- if first.rstrip().endswith('.'):
- continue
- if token_string in '+-*/':
- fixed = first + ' \\' + '\n' + second
- else:
- fixed = first + '\n' + second
-
- # Only fix if syntax is okay.
- if check_syntax(normalize_multiline(fixed)
- if aggressive else fixed):
- yield indentation + fixed
-
-
- def _is_binary_operator(token_type, text):
- return ((token_type == tokenize.OP or text in ['and', 'or']) and
- text not in '()[]{},:.;@=%~')
-
-
- # A convenient way to handle tokens.
- Token = collections.namedtuple('Token', ['token_type', 'token_string',
- 'spos', 'epos', 'line'])
-
-
- class ReformattedLines(object):
-
- """The reflowed lines of atoms.
-
- Each part of the line is represented as an "atom." They can be moved
- around when need be to get the optimal formatting.
-
- """
-
- ###########################################################################
- # Private Classes
-
- class _Indent(object):
-
- """Represent an indentation in the atom stream."""
-
- def __init__(self, indent_amt):
- self._indent_amt = indent_amt
-
- def emit(self):
- return ' ' * self._indent_amt
-
- @property
- def size(self):
- return self._indent_amt
-
- class _Space(object):
-
- """Represent a space in the atom stream."""
-
- def emit(self):
- return ' '
-
- @property
- def size(self):
- return 1
-
- class _LineBreak(object):
-
- """Represent a line break in the atom stream."""
-
- def emit(self):
- return '\n'
-
- @property
- def size(self):
- return 0
-
- def __init__(self, max_line_length):
- self._max_line_length = max_line_length
- self._lines = []
- self._bracket_depth = 0
- self._prev_item = None
- self._prev_prev_item = None
-
- def __repr__(self):
- return self.emit()
-
- ###########################################################################
- # Public Methods
-
- def add(self, obj, indent_amt, break_after_open_bracket):
- if isinstance(obj, Atom):
- self._add_item(obj, indent_amt)
- return
-
- self._add_container(obj, indent_amt, break_after_open_bracket)
-
- def add_comment(self, item):
- num_spaces = 2
- if len(self._lines) > 1:
- if isinstance(self._lines[-1], self._Space):
- num_spaces -= 1
- if len(self._lines) > 2:
- if isinstance(self._lines[-2], self._Space):
- num_spaces -= 1
-
- while num_spaces > 0:
- self._lines.append(self._Space())
- num_spaces -= 1
- self._lines.append(item)
-
- def add_indent(self, indent_amt):
- self._lines.append(self._Indent(indent_amt))
-
- def add_line_break(self, indent):
- self._lines.append(self._LineBreak())
- self.add_indent(len(indent))
-
- def add_line_break_at(self, index, indent_amt):
- self._lines.insert(index, self._LineBreak())
- self._lines.insert(index + 1, self._Indent(indent_amt))
-
- def add_space_if_needed(self, curr_text, equal=False):
- if (
- not self._lines or isinstance(
- self._lines[-1], (self._LineBreak, self._Indent, self._Space))
- ):
- return
-
- prev_text = str(self._prev_item)
- prev_prev_text = (
- str(self._prev_prev_item) if self._prev_prev_item else '')
-
- if (
- # The previous item was a keyword or identifier and the current
- # item isn't an operator that doesn't require a space.
- ((self._prev_item.is_keyword or self._prev_item.is_string or
- self._prev_item.is_name or self._prev_item.is_number) and
- (curr_text[0] not in '([{.,:}])' or
- (curr_text[0] == '=' and equal))) or
-
- # Don't place spaces around a '.', unless it's in an 'import'
- # statement.
- ((prev_prev_text != 'from' and prev_text[-1] != '.' and
- curr_text != 'import') and
-
- # Don't place a space before a colon.
- curr_text[0] != ':' and
-
- # Don't split up ending brackets by spaces.
- ((prev_text[-1] in '}])' and curr_text[0] not in '.,}])') or
-
- # Put a space after a colon or comma.
- prev_text[-1] in ':,' or
-
- # Put space around '=' if asked to.
- (equal and prev_text == '=') or
-
- # Put spaces around non-unary arithmetic operators.
- ((self._prev_prev_item and
- (prev_text not in '+-' and
- (self._prev_prev_item.is_name or
- self._prev_prev_item.is_number or
- self._prev_prev_item.is_string)) and
- prev_text in ('+', '-', '%', '*', '/', '//', '**', 'in')))))
- ):
- self._lines.append(self._Space())
-
- def previous_item(self):
- """Return the previous non-whitespace item."""
- return self._prev_item
-
- def fits_on_current_line(self, item_extent):
- return self.current_size() + item_extent <= self._max_line_length
-
- def current_size(self):
- """The size of the current line minus the indentation."""
- size = 0
- for item in reversed(self._lines):
- size += item.size
- if isinstance(item, self._LineBreak):
- break
-
- return size
-
- def line_empty(self):
- return (self._lines and
- isinstance(self._lines[-1],
- (self._LineBreak, self._Indent)))
-
- def emit(self):
- string = ''
- for item in self._lines:
- if isinstance(item, self._LineBreak):
- string = string.rstrip()
- string += item.emit()
-
- return string.rstrip() + '\n'
-
- ###########################################################################
- # Private Methods
-
- def _add_item(self, item, indent_amt):
- """Add an item to the line.
-
- Reflow the line to get the best formatting after the item is
- inserted. The bracket depth indicates if the item is being
- inserted inside of a container or not.
-
- """
- if self._prev_item and self._prev_item.is_string and item.is_string:
- # Place consecutive string literals on separate lines.
- self._lines.append(self._LineBreak())
- self._lines.append(self._Indent(indent_amt))
-
- item_text = str(item)
- if self._lines and self._bracket_depth:
- # Adding the item into a container.
- self._prevent_default_initializer_splitting(item, indent_amt)
-
- if item_text in '.,)]}':
- self._split_after_delimiter(item, indent_amt)
-
- elif self._lines and not self.line_empty():
- # Adding the item outside of a container.
- if self.fits_on_current_line(len(item_text)):
- self._enforce_space(item)
-
- else:
- # Line break for the new item.
- self._lines.append(self._LineBreak())
- self._lines.append(self._Indent(indent_amt))
-
- self._lines.append(item)
- self._prev_item, self._prev_prev_item = item, self._prev_item
-
- if item_text in '([{':
- self._bracket_depth += 1
-
- elif item_text in '}])':
- self._bracket_depth -= 1
- assert self._bracket_depth >= 0
-
- def _add_container(self, container, indent_amt, break_after_open_bracket):
- actual_indent = indent_amt + 1
-
- if (
- str(self._prev_item) != '=' and
- not self.line_empty() and
- not self.fits_on_current_line(
- container.size + self._bracket_depth + 2)
- ):
-
- if str(container)[0] == '(' and self._prev_item.is_name:
- # Don't split before the opening bracket of a call.
- break_after_open_bracket = True
- actual_indent = indent_amt + 4
- elif (
- break_after_open_bracket or
- str(self._prev_item) not in '([{'
- ):
- # If the container doesn't fit on the current line and the
- # current line isn't empty, place the container on the next
- # line.
- self._lines.append(self._LineBreak())
- self._lines.append(self._Indent(indent_amt))
- break_after_open_bracket = False
- else:
- actual_indent = self.current_size() + 1
- break_after_open_bracket = False
-
- if isinstance(container, (ListComprehension, IfExpression)):
- actual_indent = indent_amt
-
- # Increase the continued indentation only if recursing on a
- # container.
- container.reflow(self, ' ' * actual_indent,
- break_after_open_bracket=break_after_open_bracket)
-
- def _prevent_default_initializer_splitting(self, item, indent_amt):
- """Prevent splitting between a default initializer.
-
- When there is a default initializer, it's best to keep it all on
- the same line. It's nicer and more readable, even if it goes
- over the maximum allowable line length. This goes back along the
- current line to determine if we have a default initializer, and,
- if so, to remove extraneous whitespaces and add a line
- break/indent before it if needed.
-
- """
- if str(item) == '=':
- # This is the assignment in the initializer. Just remove spaces for
- # now.
- self._delete_whitespace()
- return
-
- if (not self._prev_item or not self._prev_prev_item or
- str(self._prev_item) != '='):
- return
-
- self._delete_whitespace()
- prev_prev_index = self._lines.index(self._prev_prev_item)
-
- if (
- isinstance(self._lines[prev_prev_index - 1], self._Indent) or
- self.fits_on_current_line(item.size + 1)
- ):
- # The default initializer is already the only item on this line.
- # Don't insert a newline here.
- return
-
- # Replace the space with a newline/indent combo.
- if isinstance(self._lines[prev_prev_index - 1], self._Space):
- del self._lines[prev_prev_index - 1]
-
- self.add_line_break_at(self._lines.index(self._prev_prev_item),
- indent_amt)
-
- def _split_after_delimiter(self, item, indent_amt):
- """Split the line only after a delimiter."""
- self._delete_whitespace()
-
- if self.fits_on_current_line(item.size):
- return
-
- last_space = None
- for current_item in reversed(self._lines):
- if (
- last_space and
- (not isinstance(current_item, Atom) or
- not current_item.is_colon)
- ):
- break
- else:
- last_space = None
- if isinstance(current_item, self._Space):
- last_space = current_item
- if isinstance(current_item, (self._LineBreak, self._Indent)):
- return
-
- if not last_space:
- return
-
- self.add_line_break_at(self._lines.index(last_space), indent_amt)
-
- def _enforce_space(self, item):
- """Enforce a space in certain situations.
-
- There are cases where we will want a space where normally we
- wouldn't put one. This just enforces the addition of a space.
-
- """
- if isinstance(self._lines[-1],
- (self._Space, self._LineBreak, self._Indent)):
- return
-
- if not self._prev_item:
- return
-
- item_text = str(item)
- prev_text = str(self._prev_item)
-
- # Prefer a space around a '.' in an import statement, and between the
- # 'import' and '('.
- if (
- (item_text == '.' and prev_text == 'from') or
- (item_text == 'import' and prev_text == '.') or
- (item_text == '(' and prev_text == 'import')
- ):
- self._lines.append(self._Space())
-
- def _delete_whitespace(self):
- """Delete all whitespace from the end of the line."""
- while isinstance(self._lines[-1], (self._Space, self._LineBreak,
- self._Indent)):
- del self._lines[-1]
-
-
- class Atom(object):
-
- """The smallest unbreakable unit that can be reflowed."""
-
- def __init__(self, atom):
- self._atom = atom
-
- def __repr__(self):
- return self._atom.token_string
-
- def __len__(self):
- return self.size
-
- def reflow(
- self, reflowed_lines, continued_indent, extent,
- break_after_open_bracket=False,
- is_list_comp_or_if_expr=False,
- next_is_dot=False
- ):
- if self._atom.token_type == tokenize.COMMENT:
- reflowed_lines.add_comment(self)
- return
-
- total_size = extent if extent else self.size
-
- if self._atom.token_string not in ',:([{}])':
- # Some atoms will need an extra 1-sized space token after them.
- total_size += 1
-
- prev_item = reflowed_lines.previous_item()
- if (
- not is_list_comp_or_if_expr and
- not reflowed_lines.fits_on_current_line(total_size) and
- not (next_is_dot and
- reflowed_lines.fits_on_current_line(self.size + 1)) and
- not reflowed_lines.line_empty() and
- not self.is_colon and
- not (prev_item and prev_item.is_name and
- str(self) == '(')
- ):
- # Start a new line if there is already something on the line and
- # adding this atom would make it go over the max line length.
- reflowed_lines.add_line_break(continued_indent)
- else:
- reflowed_lines.add_space_if_needed(str(self))
-
- reflowed_lines.add(self, len(continued_indent),
- break_after_open_bracket)
-
- def emit(self):
- return self.__repr__()
-
- @property
- def is_keyword(self):
- return keyword.iskeyword(self._atom.token_string)
-
- @property
- def is_string(self):
- return self._atom.token_type == tokenize.STRING
-
- @property
- def is_name(self):
- return self._atom.token_type == tokenize.NAME
-
- @property
- def is_number(self):
- return self._atom.token_type == tokenize.NUMBER
-
- @property
- def is_comma(self):
- return self._atom.token_string == ','
-
- @property
- def is_colon(self):
- return self._atom.token_string == ':'
-
- @property
- def size(self):
- return len(self._atom.token_string)
-
-
- class Container(object):
-
- """Base class for all container types."""
-
- def __init__(self, items):
- self._items = items
-
- def __repr__(self):
- string = ''
- last_was_keyword = False
-
- for item in self._items:
- if item.is_comma:
- string += ', '
- elif item.is_colon:
- string += ': '
- else:
- item_string = str(item)
- if (
- string and
- (last_was_keyword or
- (not string.endswith(tuple('([{,.:}]) ')) and
- not item_string.startswith(tuple('([{,.:}])'))))
- ):
- string += ' '
- string += item_string
-
- last_was_keyword = item.is_keyword
- return string
-
- def __iter__(self):
- for element in self._items:
- yield element
-
- def __getitem__(self, idx):
- return self._items[idx]
-
- def reflow(self, reflowed_lines, continued_indent,
- break_after_open_bracket=False):
- last_was_container = False
- for (index, item) in enumerate(self._items):
- next_item = get_item(self._items, index + 1)
-
- if isinstance(item, Atom):
- is_list_comp_or_if_expr = (
- isinstance(self, (ListComprehension, IfExpression)))
- item.reflow(reflowed_lines, continued_indent,
- self._get_extent(index),
- is_list_comp_or_if_expr=is_list_comp_or_if_expr,
- next_is_dot=(next_item and
- str(next_item) == '.'))
- if last_was_container and item.is_comma:
- reflowed_lines.add_line_break(continued_indent)
- last_was_container = False
- else: # isinstance(item, Container)
- reflowed_lines.add(item, len(continued_indent),
- break_after_open_bracket)
- last_was_container = not isinstance(item, (ListComprehension,
- IfExpression))
-
- if (
- break_after_open_bracket and index == 0 and
- # Prefer to keep empty containers together instead of
- # separating them.
- str(item) == self.open_bracket and
- (not next_item or str(next_item) != self.close_bracket) and
- (len(self._items) != 3 or not isinstance(next_item, Atom))
- ):
- reflowed_lines.add_line_break(continued_indent)
- break_after_open_bracket = False
- else:
- next_next_item = get_item(self._items, index + 2)
- if (
- str(item) not in ['.', '%', 'in'] and
- next_item and not isinstance(next_item, Container) and
- str(next_item) != ':' and
- next_next_item and (not isinstance(next_next_item, Atom) or
- str(next_item) == 'not') and
- not reflowed_lines.line_empty() and
- not reflowed_lines.fits_on_current_line(
- self._get_extent(index + 1) + 2)
- ):
- reflowed_lines.add_line_break(continued_indent)
-
- def _get_extent(self, index):
- """The extent of the full element.
-
- E.g., the length of a function call or keyword.
-
- """
- extent = 0
- prev_item = get_item(self._items, index - 1)
- seen_dot = prev_item and str(prev_item) == '.'
- while index < len(self._items):
- item = get_item(self._items, index)
- index += 1
-
- if isinstance(item, (ListComprehension, IfExpression)):
- break
-
- if isinstance(item, Container):
- if prev_item and prev_item.is_name:
- if seen_dot:
- extent += 1
- else:
- extent += item.size
-
- prev_item = item
- continue
- elif (str(item) not in ['.', '=', ':', 'not'] and
- not item.is_name and not item.is_string):
- break
-
- if str(item) == '.':
- seen_dot = True
-
- extent += item.size
- prev_item = item
-
- return extent
-
- @property
- def is_string(self):
- return False
-
- @property
- def size(self):
- return len(self.__repr__())
-
- @property
- def is_keyword(self):
- return False
-
- @property
- def is_name(self):
- return False
-
- @property
- def is_comma(self):
- return False
-
- @property
- def is_colon(self):
- return False
-
- @property
- def open_bracket(self):
- return None
-
- @property
- def close_bracket(self):
- return None
-
-
- class Tuple(Container):
-
- """A high-level representation of a tuple."""
-
- @property
- def open_bracket(self):
- return '('
-
- @property
- def close_bracket(self):
- return ')'
-
-
- class List(Container):
-
- """A high-level representation of a list."""
-
- @property
- def open_bracket(self):
- return '['
-
- @property
- def close_bracket(self):
- return ']'
-
-
- class DictOrSet(Container):
-
- """A high-level representation of a dictionary or set."""
-
- @property
- def open_bracket(self):
- return '{'
-
- @property
- def close_bracket(self):
- return '}'
-
-
- class ListComprehension(Container):
-
- """A high-level representation of a list comprehension."""
-
- @property
- def size(self):
- length = 0
- for item in self._items:
- if isinstance(item, IfExpression):
- break
- length += item.size
- return length
-
-
- class IfExpression(Container):
-
- """A high-level representation of an if-expression."""
-
-
- def _parse_container(tokens, index, for_or_if=None):
- """Parse a high-level container, such as a list, tuple, etc."""
-
- # Store the opening bracket.
- items = [Atom(Token(*tokens[index]))]
- index += 1
-
- num_tokens = len(tokens)
- while index < num_tokens:
- tok = Token(*tokens[index])
-
- if tok.token_string in ',)]}':
- # First check if we're at the end of a list comprehension or
- # if-expression. Don't add the ending token as part of the list
- # comprehension or if-expression, because they aren't part of those
- # constructs.
- if for_or_if == 'for':
- return (ListComprehension(items), index - 1)
-
- elif for_or_if == 'if':
- return (IfExpression(items), index - 1)
-
- # We've reached the end of a container.
- items.append(Atom(tok))
-
- # If not, then we are at the end of a container.
- if tok.token_string == ')':
- # The end of a tuple.
- return (Tuple(items), index)
-
- elif tok.token_string == ']':
- # The end of a list.
- return (List(items), index)
-
- elif tok.token_string == '}':
- # The end of a dictionary or set.
- return (DictOrSet(items), index)
-
- elif tok.token_string in '([{':
- # A sub-container is being defined.
- (container, index) = _parse_container(tokens, index)
- items.append(container)
-
- elif tok.token_string == 'for':
- (container, index) = _parse_container(tokens, index, 'for')
- items.append(container)
-
- elif tok.token_string == 'if':
- (container, index) = _parse_container(tokens, index, 'if')
- items.append(container)
-
- else:
- items.append(Atom(tok))
-
- index += 1
-
- return (None, None)
-
-
- def _parse_tokens(tokens):
- """Parse the tokens.
-
- This converts the tokens into a form where we can manipulate them
- more easily.
-
- """
-
- index = 0
- parsed_tokens = []
-
- num_tokens = len(tokens)
- while index < num_tokens:
- tok = Token(*tokens[index])
-
- assert tok.token_type != token.INDENT
- if tok.token_type == tokenize.NEWLINE:
- # There's only one newline and it's at the end.
- break
-
- if tok.token_string in '([{':
- (container, index) = _parse_container(tokens, index)
- if not container:
- return None
- parsed_tokens.append(container)
- else:
- parsed_tokens.append(Atom(tok))
-
- index += 1
-
- return parsed_tokens
-
-
- def _reflow_lines(parsed_tokens, indentation, max_line_length,
- start_on_prefix_line):
- """Reflow the lines so that it looks nice."""
-
- if str(parsed_tokens[0]) == 'def':
- # A function definition gets indented a bit more.
- continued_indent = indentation + ' ' * 2 * DEFAULT_INDENT_SIZE
- else:
- continued_indent = indentation + ' ' * DEFAULT_INDENT_SIZE
-
- break_after_open_bracket = not start_on_prefix_line
-
- lines = ReformattedLines(max_line_length)
- lines.add_indent(len(indentation.lstrip('\r\n')))
-
- if not start_on_prefix_line:
- # If splitting after the opening bracket will cause the first element
- # to be aligned weirdly, don't try it.
- first_token = get_item(parsed_tokens, 0)
- second_token = get_item(parsed_tokens, 1)
-
- if (
- first_token and second_token and
- str(second_token)[0] == '(' and
- len(indentation) + len(first_token) + 1 == len(continued_indent)
- ):
- return None
-
- for item in parsed_tokens:
- lines.add_space_if_needed(str(item), equal=True)
-
- save_continued_indent = continued_indent
- if start_on_prefix_line and isinstance(item, Container):
- start_on_prefix_line = False
- continued_indent = ' ' * (lines.current_size() + 1)
-
- item.reflow(lines, continued_indent, break_after_open_bracket)
- continued_indent = save_continued_indent
-
- return lines.emit()
-
-
- def _shorten_line_at_tokens_new(tokens, source, indentation,
- max_line_length):
- """Shorten the line taking its length into account.
-
- The input is expected to be free of newlines except for inside
- multiline strings and at the end.
-
- """
- # Yield the original source so to see if it's a better choice than the
- # shortened candidate lines we generate here.
- yield indentation + source
-
- parsed_tokens = _parse_tokens(tokens)
-
- if parsed_tokens:
- # Perform two reflows. The first one starts on the same line as the
- # prefix. The second starts on the line after the prefix.
- fixed = _reflow_lines(parsed_tokens, indentation, max_line_length,
- start_on_prefix_line=True)
- if fixed and check_syntax(normalize_multiline(fixed.lstrip())):
- yield fixed
-
- fixed = _reflow_lines(parsed_tokens, indentation, max_line_length,
- start_on_prefix_line=False)
- if fixed and check_syntax(normalize_multiline(fixed.lstrip())):
- yield fixed
-
-
- def _shorten_line_at_tokens(tokens, source, indentation, indent_word,
- key_token_strings, aggressive):
- """Separate line by breaking at tokens in key_token_strings.
-
- The input is expected to be free of newlines except for inside
- multiline strings and at the end.
-
- """
- offsets = []
- for (index, _t) in enumerate(token_offsets(tokens)):
- (token_type,
- token_string,
- start_offset,
- end_offset) = _t
-
- assert token_type != token.INDENT
-
- if token_string in key_token_strings:
- # Do not break in containers with zero or one items.
- unwanted_next_token = {
- '(': ')',
- '[': ']',
- '{': '}'}.get(token_string)
- if unwanted_next_token:
- if (
- get_item(tokens,
- index + 1,
- default=[None, None])[1] == unwanted_next_token or
- get_item(tokens,
- index + 2,
- default=[None, None])[1] == unwanted_next_token
- ):
- continue
-
- if (
- index > 2 and token_string == '(' and
- tokens[index - 1][1] in ',(%['
- ):
- # Don't split after a tuple start, or before a tuple start if
- # the tuple is in a list.
- continue
-
- if end_offset < len(source) - 1:
- # Don't split right before newline.
- offsets.append(end_offset)
- else:
- # Break at adjacent strings. These were probably meant to be on
- # separate lines in the first place.
- previous_token = get_item(tokens, index - 1)
- if (
- token_type == tokenize.STRING and
- previous_token and previous_token[0] == tokenize.STRING
- ):
- offsets.append(start_offset)
-
- current_indent = None
- fixed = None
- for line in split_at_offsets(source, offsets):
- if fixed:
- fixed += '\n' + current_indent + line
-
- for symbol in '([{':
- if line.endswith(symbol):
- current_indent += indent_word
- else:
- # First line.
- fixed = line
- assert not current_indent
- current_indent = indent_word
-
- assert fixed is not None
-
- if check_syntax(normalize_multiline(fixed)
- if aggressive > 1 else fixed):
- return indentation + fixed
-
- return None
-
-
- def token_offsets(tokens):
- """Yield tokens and offsets."""
- end_offset = 0
- previous_end_row = 0
- previous_end_column = 0
- for t in tokens:
- token_type = t[0]
- token_string = t[1]
- (start_row, start_column) = t[2]
- (end_row, end_column) = t[3]
-
- # Account for the whitespace between tokens.
- end_offset += start_column
- if previous_end_row == start_row:
- end_offset -= previous_end_column
-
- # Record the start offset of the token.
- start_offset = end_offset
-
- # Account for the length of the token itself.
- end_offset += len(token_string)
-
- yield (token_type,
- token_string,
- start_offset,
- end_offset)
-
- previous_end_row = end_row
- previous_end_column = end_column
-
-
- def normalize_multiline(line):
- """Normalize multiline-related code that will cause syntax error.
-
- This is for purposes of checking syntax.
-
- """
- if line.startswith('def ') and line.rstrip().endswith(':'):
- return line + ' pass'
- elif line.startswith('return '):
- return 'def _(): ' + line
- elif line.startswith('@'):
- return line + 'def _(): pass'
- elif line.startswith('class '):
- return line + ' pass'
- elif line.startswith(('if ', 'elif ', 'for ', 'while ')):
- return line + ' pass'
-
- return line
-
-
- def fix_whitespace(line, offset, replacement):
- """Replace whitespace at offset and return fixed line."""
- # Replace escaped newlines too
- left = line[:offset].rstrip('\n\r \t\\')
- right = line[offset:].lstrip('\n\r \t\\')
- if right.startswith('#'):
- return line
-
- return left + replacement + right
-
-
- def _execute_pep8(pep8_options, source):
- """Execute pycodestyle via python method calls."""
- class QuietReport(pycodestyle.BaseReport):
-
- """Version of checker that does not print."""
-
- def __init__(self, options):
- super(QuietReport, self).__init__(options)
- self.__full_error_results = []
-
- def error(self, line_number, offset, text, check):
- """Collect errors."""
- code = super(QuietReport, self).error(line_number,
- offset,
- text,
- check)
- if code:
- self.__full_error_results.append(
- {'id': code,
- 'line': line_number,
- 'column': offset + 1,
- 'info': text})
-
- def full_error_results(self):
- """Return error results in detail.
-
- Results are in the form of a list of dictionaries. Each
- dictionary contains 'id', 'line', 'column', and 'info'.
-
- """
- return self.__full_error_results
-
- checker = pycodestyle.Checker('', lines=source, reporter=QuietReport,
- **pep8_options)
- checker.check_all()
- return checker.report.full_error_results()
-
-
- def _remove_leading_and_normalize(line, with_rstrip=True):
- # ignore FF in first lstrip()
- if with_rstrip:
- return line.lstrip(' \t\v').rstrip(CR + LF) + '\n'
- return line.lstrip(' \t\v')
-
-
- class Reindenter(object):
-
- """Reindents badly-indented code to uniformly use four-space indentation.
-
- Released to the public domain, by Tim Peters, 03 October 2000.
-
- """
-
- def __init__(self, input_text, leave_tabs=False):
- sio = io.StringIO(input_text)
- source_lines = sio.readlines()
-
- self.string_content_line_numbers = multiline_string_lines(input_text)
-
- # File lines, rstripped & tab-expanded. Dummy at start is so
- # that we can use tokenize's 1-based line numbering easily.
- # Note that a line is all-blank iff it is a newline.
- self.lines = []
- for line_number, line in enumerate(source_lines, start=1):
- # Do not modify if inside a multiline string.
- if line_number in self.string_content_line_numbers:
- self.lines.append(line)
- else:
- # Only expand leading tabs.
- with_rstrip = line_number != len(source_lines)
- if leave_tabs:
- self.lines.append(
- _get_indentation(line) +
- _remove_leading_and_normalize(line, with_rstrip)
- )
- else:
- self.lines.append(
- _get_indentation(line).expandtabs() +
- _remove_leading_and_normalize(line, with_rstrip)
- )
-
- self.lines.insert(0, None)
- self.index = 1 # index into self.lines of next line
- self.input_text = input_text
-
- def run(self, indent_size=DEFAULT_INDENT_SIZE):
- """Fix indentation and return modified line numbers.
-
- Line numbers are indexed at 1.
-
- """
- if indent_size < 1:
- return self.input_text
-
- try:
- stats = _reindent_stats(tokenize.generate_tokens(self.getline))
- except (SyntaxError, tokenize.TokenError):
- return self.input_text
- # Remove trailing empty lines.
- lines = self.lines
- # Sentinel.
- stats.append((len(lines), 0))
- # Map count of leading spaces to # we want.
- have2want = {}
- # Program after transformation.
- after = []
- # Copy over initial empty lines -- there's nothing to do until
- # we see a line with *something* on it.
- i = stats[0][0]
- after.extend(lines[1:i])
- for i in range(len(stats) - 1):
- thisstmt, thislevel = stats[i]
- nextstmt = stats[i + 1][0]
- have = _leading_space_count(lines[thisstmt])
- want = thislevel * indent_size
- if want < 0:
- # A comment line.
- if have:
- # An indented comment line. If we saw the same
- # indentation before, reuse what it most recently
- # mapped to.
- want = have2want.get(have, -1)
- if want < 0:
- # Then it probably belongs to the next real stmt.
- for j in range(i + 1, len(stats) - 1):
- jline, jlevel = stats[j]
- if jlevel >= 0:
- if have == _leading_space_count(lines[jline]):
- want = jlevel * indent_size
- break
- # Maybe it's a hanging comment like this one,
- if want < 0:
- # in which case we should shift it like its base
- # line got shifted.
- for j in range(i - 1, -1, -1):
- jline, jlevel = stats[j]
- if jlevel >= 0:
- want = (have + _leading_space_count(
- after[jline - 1]) -
- _leading_space_count(lines[jline]))
- break
- if want < 0:
- # Still no luck -- leave it alone.
- want = have
- else:
- want = 0
- assert want >= 0
- have2want[have] = want
- diff = want - have
- if diff == 0 or have == 0:
- after.extend(lines[thisstmt:nextstmt])
- else:
- for line_number, line in enumerate(lines[thisstmt:nextstmt],
- start=thisstmt):
- if line_number in self.string_content_line_numbers:
- after.append(line)
- elif diff > 0:
- if line == '\n':
- after.append(line)
- else:
- after.append(' ' * diff + line)
- else:
- remove = min(_leading_space_count(line), -diff)
- after.append(line[remove:])
-
- return ''.join(after)
-
- def getline(self):
- """Line-getter for tokenize."""
- if self.index >= len(self.lines):
- line = ''
- else:
- line = self.lines[self.index]
- self.index += 1
- return line
-
-
- def _reindent_stats(tokens):
- """Return list of (lineno, indentlevel) pairs.
-
- One for each stmt and comment line. indentlevel is -1 for comment
- lines, as a signal that tokenize doesn't know what to do about them;
- indeed, they're our headache!
-
- """
- find_stmt = 1 # Next token begins a fresh stmt?
- level = 0 # Current indent level.
- stats = []
-
- for t in tokens:
- token_type = t[0]
- sline = t[2][0]
- line = t[4]
-
- if token_type == tokenize.NEWLINE:
- # A program statement, or ENDMARKER, will eventually follow,
- # after some (possibly empty) run of tokens of the form
- # (NL | COMMENT)* (INDENT | DEDENT+)?
- find_stmt = 1
-
- elif token_type == tokenize.INDENT:
- find_stmt = 1
- level += 1
-
- elif token_type == tokenize.DEDENT:
- find_stmt = 1
- level -= 1
-
- elif token_type == tokenize.COMMENT:
- if find_stmt:
- stats.append((sline, -1))
- # But we're still looking for a new stmt, so leave
- # find_stmt alone.
-
- elif token_type == tokenize.NL:
- pass
-
- elif find_stmt:
- # This is the first "real token" following a NEWLINE, so it
- # must be the first token of the next program statement, or an
- # ENDMARKER.
- find_stmt = 0
- if line: # Not endmarker.
- stats.append((sline, level))
-
- return stats
-
-
- def _leading_space_count(line):
- """Return number of leading spaces in line."""
- i = 0
- while i < len(line) and line[i] == ' ':
- i += 1
- return i
-
-
- def refactor_with_2to3(source_text, fixer_names, filename=''):
- """Use lib2to3 to refactor the source.
-
- Return the refactored source code.
-
- """
- from lib2to3.refactor import RefactoringTool
- fixers = ['lib2to3.fixes.fix_' + name for name in fixer_names]
- tool = RefactoringTool(fixer_names=fixers, explicit=fixers)
-
- from lib2to3.pgen2 import tokenize as lib2to3_tokenize
- try:
- # The name parameter is necessary particularly for the "import" fixer.
- return str(tool.refactor_string(source_text, name=filename))
- except lib2to3_tokenize.TokenError:
- return source_text
-
-
- def check_syntax(code):
- """Return True if syntax is okay."""
- try:
- return compile(code, '<string>', 'exec', dont_inherit=True)
- except (SyntaxError, TypeError, ValueError):
- return False
-
-
- def find_with_line_numbers(pattern, contents):
- """A wrapper around 're.finditer' to find line numbers.
-
- Returns a list of line numbers where pattern was found in contents.
- """
- matches = list(re.finditer(pattern, contents))
- if not matches:
- return []
-
- end = matches[-1].start()
-
- # -1 so a failed `rfind` maps to the first line.
- newline_offsets = {
- -1: 0
- }
- for line_num, m in enumerate(re.finditer(r'\n', contents), 1):
- offset = m.start()
- if offset > end:
- break
- newline_offsets[offset] = line_num
-
- def get_line_num(match, contents):
- """Get the line number of string in a files contents.
-
- Failing to find the newline is OK, -1 maps to 0
-
- """
- newline_offset = contents.rfind('\n', 0, match.start())
- return newline_offsets[newline_offset]
-
- return [get_line_num(match, contents) + 1 for match in matches]
-
-
- def get_disabled_ranges(source):
- """Returns a list of tuples representing the disabled ranges.
-
- If disabled and no re-enable will disable for rest of file.
-
- """
- enable_line_nums = find_with_line_numbers(ENABLE_REGEX, source)
- disable_line_nums = find_with_line_numbers(DISABLE_REGEX, source)
- total_lines = len(re.findall("\n", source)) + 1
-
- enable_commands = {}
- for num in enable_line_nums:
- enable_commands[num] = True
- for num in disable_line_nums:
- enable_commands[num] = False
-
- disabled_ranges = []
- currently_enabled = True
- disabled_start = None
-
- for line, commanded_enabled in sorted(enable_commands.items()):
- if commanded_enabled is False and currently_enabled is True:
- disabled_start = line
- currently_enabled = False
- elif commanded_enabled is True and currently_enabled is False:
- disabled_ranges.append((disabled_start, line))
- currently_enabled = True
-
- if currently_enabled is False:
- disabled_ranges.append((disabled_start, total_lines))
-
- return disabled_ranges
-
-
- def filter_disabled_results(result, disabled_ranges):
- """Filter out reports based on tuple of disabled ranges.
-
- """
- line = result['line']
- for disabled_range in disabled_ranges:
- if disabled_range[0] <= line <= disabled_range[1]:
- return False
- return True
-
-
- def filter_results(source, results, aggressive):
- """Filter out spurious reports from pycodestyle.
-
- If aggressive is True, we allow possibly unsafe fixes (E711, E712).
-
- """
- non_docstring_string_line_numbers = multiline_string_lines(
- source, include_docstrings=False)
- all_string_line_numbers = multiline_string_lines(
- source, include_docstrings=True)
-
- commented_out_code_line_numbers = commented_out_code_lines(source)
-
- # Filter out the disabled ranges
- disabled_ranges = get_disabled_ranges(source)
- if disabled_ranges:
- results = [
- result for result in results if filter_disabled_results(
- result,
- disabled_ranges,
- )
- ]
-
- has_e901 = any(result['id'].lower() == 'e901' for result in results)
-
- for r in results:
- issue_id = r['id'].lower()
-
- if r['line'] in non_docstring_string_line_numbers:
- if issue_id.startswith(('e1', 'e501', 'w191')):
- continue
-
- if r['line'] in all_string_line_numbers:
- if issue_id in ['e501']:
- continue
-
- # We must offset by 1 for lines that contain the trailing contents of
- # multiline strings.
- if not aggressive and (r['line'] + 1) in all_string_line_numbers:
- # Do not modify multiline strings in non-aggressive mode. Remove
- # trailing whitespace could break doctests.
- if issue_id.startswith(('w29', 'w39')):
- continue
-
- if aggressive <= 0:
- if issue_id.startswith(('e711', 'e72', 'w6')):
- continue
-
- if aggressive <= 1:
- if issue_id.startswith(('e712', 'e713', 'e714')):
- continue
-
- if aggressive <= 2:
- if issue_id.startswith(('e704')):
- continue
-
- if r['line'] in commented_out_code_line_numbers:
- if issue_id.startswith(('e261', 'e262', 'e501')):
- continue
-
- # Do not touch indentation if there is a token error caused by
- # incomplete multi-line statement. Otherwise, we risk screwing up the
- # indentation.
- if has_e901:
- if issue_id.startswith(('e1', 'e7')):
- continue
-
- yield r
-
-
- def multiline_string_lines(source, include_docstrings=False):
- """Return line numbers that are within multiline strings.
-
- The line numbers are indexed at 1.
-
- Docstrings are ignored.
-
- """
- line_numbers = set()
- previous_token_type = ''
- try:
- for t in generate_tokens(source):
- token_type = t[0]
- start_row = t[2][0]
- end_row = t[3][0]
-
- if token_type == tokenize.STRING and start_row != end_row:
- if (
- include_docstrings or
- previous_token_type != tokenize.INDENT
- ):
- # We increment by one since we want the contents of the
- # string.
- line_numbers |= set(range(1 + start_row, 1 + end_row))
-
- previous_token_type = token_type
- except (SyntaxError, tokenize.TokenError):
- pass
-
- return line_numbers
-
-
- def commented_out_code_lines(source):
- """Return line numbers of comments that are likely code.
-
- Commented-out code is bad practice, but modifying it just adds even
- more clutter.
-
- """
- line_numbers = []
- try:
- for t in generate_tokens(source):
- token_type = t[0]
- token_string = t[1]
- start_row = t[2][0]
- line = t[4]
-
- # Ignore inline comments.
- if not line.lstrip().startswith('#'):
- continue
-
- if token_type == tokenize.COMMENT:
- stripped_line = token_string.lstrip('#').strip()
- with warnings.catch_warnings():
- # ignore SyntaxWarning in Python3.8+
- # refs:
- # https://bugs.python.org/issue15248
- # https://docs.python.org/3.8/whatsnew/3.8.html#other-language-changes
- warnings.filterwarnings("ignore", category=SyntaxWarning)
- if (
- ' ' in stripped_line and
- '#' not in stripped_line and
- check_syntax(stripped_line)
- ):
- line_numbers.append(start_row)
- except (SyntaxError, tokenize.TokenError):
- pass
-
- return line_numbers
-
-
- def shorten_comment(line, max_line_length, last_comment=False):
- """Return trimmed or split long comment line.
-
- If there are no comments immediately following it, do a text wrap.
- Doing this wrapping on all comments in general would lead to jagged
- comment text.
-
- """
- assert len(line) > max_line_length
- line = line.rstrip()
-
- # PEP 8 recommends 72 characters for comment text.
- indentation = _get_indentation(line) + '# '
- max_line_length = min(max_line_length,
- len(indentation) + 72)
-
- MIN_CHARACTER_REPEAT = 5
- if (
- len(line) - len(line.rstrip(line[-1])) >= MIN_CHARACTER_REPEAT and
- not line[-1].isalnum()
- ):
- # Trim comments that end with things like ---------
- return line[:max_line_length] + '\n'
- elif last_comment and re.match(r'\s*#+\s*\w+', line):
- split_lines = textwrap.wrap(line.lstrip(' \t#'),
- initial_indent=indentation,
- subsequent_indent=indentation,
- width=max_line_length,
- break_long_words=False,
- break_on_hyphens=False)
- return '\n'.join(split_lines) + '\n'
-
- return line + '\n'
-
-
- def normalize_line_endings(lines, newline):
- """Return fixed line endings.
-
- All lines will be modified to use the most common line ending.
- """
- line = [line.rstrip('\n\r') + newline for line in lines]
- if line and lines[-1] == lines[-1].rstrip('\n\r'):
- line[-1] = line[-1].rstrip('\n\r')
- return line
-
-
- def mutual_startswith(a, b):
- return b.startswith(a) or a.startswith(b)
-
-
- def code_match(code, select, ignore):
- if ignore:
- assert not isinstance(ignore, str)
- for ignored_code in [c.strip() for c in ignore]:
- if mutual_startswith(code.lower(), ignored_code.lower()):
- return False
-
- if select:
- assert not isinstance(select, str)
- for selected_code in [c.strip() for c in select]:
- if mutual_startswith(code.lower(), selected_code.lower()):
- return True
- return False
-
- return True
-
-
- def fix_code(source, options=None, encoding=None, apply_config=False):
- """Return fixed source code.
-
- "encoding" will be used to decode "source" if it is a byte string.
-
- """
- options = _get_options(options, apply_config)
- # normalize
- options.ignore = [opt.upper() for opt in options.ignore]
- options.select = [opt.upper() for opt in options.select]
-
- # check ignore args
- # NOTE: If W50x is not included, add W50x because the code
- # correction result is indefinite.
- ignore_opt = options.ignore
- if not {"W50", "W503", "W504"} & set(ignore_opt):
- options.ignore.append("W50")
-
- if not isinstance(source, str):
- source = source.decode(encoding or get_encoding())
-
- sio = io.StringIO(source)
- return fix_lines(sio.readlines(), options=options)
-
-
- def _get_options(raw_options, apply_config):
- """Return parsed options."""
- if not raw_options:
- return parse_args([''], apply_config=apply_config)
-
- if isinstance(raw_options, dict):
- options = parse_args([''], apply_config=apply_config)
- for name, value in raw_options.items():
- if not hasattr(options, name):
- raise ValueError("No such option '{}'".format(name))
-
- # Check for very basic type errors.
- expected_type = type(getattr(options, name))
- if not isinstance(expected_type, (str, )):
- if isinstance(value, (str, )):
- raise ValueError(
- "Option '{}' should not be a string".format(name))
- setattr(options, name, value)
- else:
- options = raw_options
-
- return options
-
-
- def fix_lines(source_lines, options, filename=''):
- """Return fixed source code."""
- # Transform everything to line feed. Then change them back to original
- # before returning fixed source code.
- original_newline = find_newline(source_lines)
- tmp_source = ''.join(normalize_line_endings(source_lines, '\n'))
-
- # Keep a history to break out of cycles.
- previous_hashes = set()
-
- if options.line_range:
- # Disable "apply_local_fixes()" for now due to issue #175.
- fixed_source = tmp_source
- else:
- # Apply global fixes only once (for efficiency).
- fixed_source = apply_global_fixes(tmp_source,
- options,
- filename=filename)
-
- passes = 0
- long_line_ignore_cache = set()
- while hash(fixed_source) not in previous_hashes:
- if options.pep8_passes >= 0 and passes > options.pep8_passes:
- break
- passes += 1
-
- previous_hashes.add(hash(fixed_source))
-
- tmp_source = copy.copy(fixed_source)
-
- fix = FixPEP8(
- filename,
- options,
- contents=tmp_source,
- long_line_ignore_cache=long_line_ignore_cache)
-
- fixed_source = fix.fix()
-
- sio = io.StringIO(fixed_source)
- return ''.join(normalize_line_endings(sio.readlines(), original_newline))
-
-
- def fix_file(filename, options=None, output=None, apply_config=False):
- if not options:
- options = parse_args([filename], apply_config=apply_config)
-
- original_source = readlines_from_file(filename)
-
- fixed_source = original_source
-
- if options.in_place or options.diff or output:
- encoding = detect_encoding(filename)
-
- if output:
- output = LineEndingWrapper(wrap_output(output, encoding=encoding))
-
- fixed_source = fix_lines(fixed_source, options, filename=filename)
-
- if options.diff:
- new = io.StringIO(fixed_source)
- new = new.readlines()
- diff = get_diff_text(original_source, new, filename)
- if output:
- output.write(diff)
- output.flush()
- elif options.jobs > 1:
- diff = diff.encode(encoding)
- return diff
- elif options.in_place:
- original = "".join(original_source).splitlines()
- fixed = fixed_source.splitlines()
- original_source_last_line = (
- original_source[-1].split("\n")[-1] if original_source else ""
- )
- fixed_source_last_line = fixed_source.split("\n")[-1]
- if original != fixed or (
- original_source_last_line != fixed_source_last_line
- ):
- with open_with_encoding(filename, 'w', encoding=encoding) as fp:
- fp.write(fixed_source)
- return fixed_source
- return None
- else:
- if output:
- output.write(fixed_source)
- output.flush()
- return fixed_source
-
-
- def global_fixes():
- """Yield multiple (code, function) tuples."""
- for function in list(globals().values()):
- if inspect.isfunction(function):
- arguments = _get_parameters(function)
- if arguments[:1] != ['source']:
- continue
-
- code = extract_code_from_function(function)
- if code:
- yield (code, function)
-
-
- def _get_parameters(function):
- # pylint: disable=deprecated-method
- if sys.version_info.major >= 3:
- # We need to match "getargspec()", which includes "self" as the first
- # value for methods.
- # https://bugs.python.org/issue17481#msg209469
- if inspect.ismethod(function):
- function = function.__func__
-
- return list(inspect.signature(function).parameters)
- else:
- return inspect.getargspec(function)[0]
-
-
- def apply_global_fixes(source, options, where='global', filename='',
- codes=None):
- """Run global fixes on source code.
-
- These are fixes that only need be done once (unlike those in
- FixPEP8, which are dependent on pycodestyle).
-
- """
- if codes is None:
- codes = []
- if any(code_match(code, select=options.select, ignore=options.ignore)
- for code in ['E101', 'E111']):
- source = reindent(source,
- indent_size=options.indent_size,
- leave_tabs=not (
- code_match(
- 'W191',
- select=options.select,
- ignore=options.ignore)
- )
- )
-
- for (code, function) in global_fixes():
- if code_match(code, select=options.select, ignore=options.ignore):
- if options.verbose:
- print('---> Applying {} fix for {}'.format(where,
- code.upper()),
- file=sys.stderr)
- source = function(source,
- aggressive=options.aggressive)
-
- source = fix_2to3(source,
- aggressive=options.aggressive,
- select=options.select,
- ignore=options.ignore,
- filename=filename,
- where=where,
- verbose=options.verbose)
-
- return source
-
-
- def extract_code_from_function(function):
- """Return code handled by function."""
- if not function.__name__.startswith('fix_'):
- return None
-
- code = re.sub('^fix_', '', function.__name__)
- if not code:
- return None
-
- try:
- int(code[1:])
- except ValueError:
- return None
-
- return code
-
-
- def _get_package_version():
- packages = ["pycodestyle: {}".format(pycodestyle.__version__)]
- return ", ".join(packages)
-
-
- def create_parser():
- """Return command-line parser."""
- parser = argparse.ArgumentParser(description=docstring_summary(__doc__),
- prog='autopep8')
- parser.add_argument('--version', action='version',
- version='%(prog)s {} ({})'.format(
- __version__, _get_package_version()))
- parser.add_argument('-v', '--verbose', action='count',
- default=0,
- help='print verbose messages; '
- 'multiple -v result in more verbose messages')
- parser.add_argument('-d', '--diff', action='store_true',
- help='print the diff for the fixed source')
- parser.add_argument('-i', '--in-place', action='store_true',
- help='make changes to files in place')
- parser.add_argument('--global-config', metavar='filename',
- default=DEFAULT_CONFIG,
- help='path to a global pep8 config file; if this file '
- 'does not exist then this is ignored '
- '(default: {})'.format(DEFAULT_CONFIG))
- parser.add_argument('--ignore-local-config', action='store_true',
- help="don't look for and apply local config files; "
- 'if not passed, defaults are updated with any '
- "config files in the project's root directory")
- parser.add_argument('-r', '--recursive', action='store_true',
- help='run recursively over directories; '
- 'must be used with --in-place or --diff')
- parser.add_argument('-j', '--jobs', type=int, metavar='n', default=1,
- help='number of parallel jobs; '
- 'match CPU count if value is less than 1')
- parser.add_argument('-p', '--pep8-passes', metavar='n',
- default=-1, type=int,
- help='maximum number of additional pep8 passes '
- '(default: infinite)')
- parser.add_argument('-a', '--aggressive', action='count', default=0,
- help='enable non-whitespace changes; '
- 'multiple -a result in more aggressive changes')
- parser.add_argument('--experimental', action='store_true',
- help='enable experimental fixes')
- parser.add_argument('--exclude', metavar='globs',
- help='exclude file/directory names that match these '
- 'comma-separated globs')
- parser.add_argument('--list-fixes', action='store_true',
- help='list codes for fixes; '
- 'used by --ignore and --select')
- parser.add_argument('--ignore', metavar='errors', default='',
- help='do not fix these errors/warnings '
- '(default: {})'.format(DEFAULT_IGNORE))
- parser.add_argument('--select', metavar='errors', default='',
- help='fix only these errors/warnings (e.g. E4,W)')
- parser.add_argument('--max-line-length', metavar='n', default=79, type=int,
- help='set maximum allowed line length '
- '(default: %(default)s)')
- parser.add_argument('--line-range', '--range', metavar='line',
- default=None, type=int, nargs=2,
- help='only fix errors found within this inclusive '
- 'range of line numbers (e.g. 1 99); '
- 'line numbers are indexed at 1')
- parser.add_argument('--indent-size', default=DEFAULT_INDENT_SIZE,
- type=int, help=argparse.SUPPRESS)
- parser.add_argument('--hang-closing', action='store_true',
- help='hang-closing option passed to pycodestyle')
- parser.add_argument('--exit-code', action='store_true',
- help='change to behavior of exit code.'
- ' default behavior of return value, 0 is no '
- 'differences, 1 is error exit. return 2 when'
- ' add this option. 2 is exists differences.')
- parser.add_argument('files', nargs='*',
- help="files to format or '-' for standard in")
-
- return parser
-
-
- def _expand_codes(codes, ignore_codes):
- """expand to individual E/W codes"""
- ret = set()
-
- is_conflict = False
- if all(
- any(
- conflicting_code.startswith(code)
- for code in codes
- )
- for conflicting_code in CONFLICTING_CODES
- ):
- is_conflict = True
-
- is_ignore_w503 = "W503" in ignore_codes
- is_ignore_w504 = "W504" in ignore_codes
-
- for code in codes:
- if code == "W":
- if is_ignore_w503 and is_ignore_w504:
- ret.update({"W1", "W2", "W3", "W505", "W6"})
- elif is_ignore_w503:
- ret.update({"W1", "W2", "W3", "W504", "W505", "W6"})
- else:
- ret.update({"W1", "W2", "W3", "W503", "W505", "W6"})
- elif code in ("W5", "W50"):
- if is_ignore_w503 and is_ignore_w504:
- ret.update({"W505"})
- elif is_ignore_w503:
- ret.update({"W504", "W505"})
- else:
- ret.update({"W503", "W505"})
- elif not (code in ("W503", "W504") and is_conflict):
- ret.add(code)
-
- return ret
-
-
- def parse_args(arguments, apply_config=False):
- """Parse command-line options."""
- parser = create_parser()
- args = parser.parse_args(arguments)
-
- if not args.files and not args.list_fixes:
- parser.exit(EXIT_CODE_ARGPARSE_ERROR, 'incorrect number of arguments')
-
- args.files = [decode_filename(name) for name in args.files]
-
- if apply_config:
- parser = read_config(args, parser)
- # prioritize settings when exist pyproject.toml's tool.autopep8 section
- try:
- parser_with_pyproject_toml = read_pyproject_toml(args, parser)
- except Exception:
- parser_with_pyproject_toml = None
- if parser_with_pyproject_toml:
- parser = parser_with_pyproject_toml
- args = parser.parse_args(arguments)
- args.files = [decode_filename(name) for name in args.files]
-
- if '-' in args.files:
- if len(args.files) > 1:
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- 'cannot mix stdin and regular files',
- )
-
- if args.diff:
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- '--diff cannot be used with standard input',
- )
-
- if args.in_place:
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- '--in-place cannot be used with standard input',
- )
-
- if args.recursive:
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- '--recursive cannot be used with standard input',
- )
-
- if len(args.files) > 1 and not (args.in_place or args.diff):
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- 'autopep8 only takes one filename as argument '
- 'unless the "--in-place" or "--diff" args are used',
- )
-
- if args.recursive and not (args.in_place or args.diff):
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- '--recursive must be used with --in-place or --diff',
- )
-
- if args.in_place and args.diff:
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- '--in-place and --diff are mutually exclusive',
- )
-
- if args.max_line_length <= 0:
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- '--max-line-length must be greater than 0',
- )
-
- if args.indent_size <= 0:
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- '--indent-size must be greater than 0',
- )
-
- if args.select:
- args.select = _expand_codes(
- _split_comma_separated(args.select),
- (_split_comma_separated(args.ignore) if args.ignore else [])
- )
-
- if args.ignore:
- args.ignore = _split_comma_separated(args.ignore)
- if all(
- not any(
- conflicting_code.startswith(ignore_code)
- for ignore_code in args.ignore
- )
- for conflicting_code in CONFLICTING_CODES
- ):
- args.ignore.update(CONFLICTING_CODES)
- elif not args.select:
- if args.aggressive:
- # Enable everything by default if aggressive.
- args.select = {'E', 'W1', 'W2', 'W3', 'W6'}
- else:
- args.ignore = _split_comma_separated(DEFAULT_IGNORE)
-
- if args.exclude:
- args.exclude = _split_comma_separated(args.exclude)
- else:
- args.exclude = {}
-
- if args.jobs < 1:
- # Do not import multiprocessing globally in case it is not supported
- # on the platform.
- import multiprocessing
- args.jobs = multiprocessing.cpu_count()
-
- if args.jobs > 1 and not (args.in_place or args.diff):
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- 'parallel jobs requires --in-place',
- )
-
- if args.line_range:
- if args.line_range[0] <= 0:
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- '--range must be positive numbers',
- )
- if args.line_range[0] > args.line_range[1]:
- parser.exit(
- EXIT_CODE_ARGPARSE_ERROR,
- 'First value of --range should be less than or equal '
- 'to the second',
- )
-
- return args
-
-
- def _get_normalize_options(args, config, section, option_list):
- for (k, v) in config.items(section):
- norm_opt = k.lstrip('-').replace('-', '_')
- if not option_list.get(norm_opt):
- continue
- opt_type = option_list[norm_opt]
- if opt_type is int:
- if v.strip() == "auto":
- # skip to special case
- if args.verbose:
- print(f"ignore config: {k}={v}")
- continue
- value = config.getint(section, k)
- elif opt_type is bool:
- value = config.getboolean(section, k)
- else:
- value = config.get(section, k)
- yield norm_opt, k, value
-
-
- def read_config(args, parser):
- """Read both user configuration and local configuration."""
- config = SafeConfigParser()
-
- try:
- if args.verbose and os.path.exists(args.global_config):
- print("read config path: {}".format(args.global_config))
- config.read(args.global_config)
-
- if not args.ignore_local_config:
- parent = tail = args.files and os.path.abspath(
- os.path.commonprefix(args.files))
- while tail:
- if config.read([os.path.join(parent, fn)
- for fn in PROJECT_CONFIG]):
- if args.verbose:
- for fn in PROJECT_CONFIG:
- config_file = os.path.join(parent, fn)
- if not os.path.exists(config_file):
- continue
- print(
- "read config path: {}".format(
- os.path.join(parent, fn)
- )
- )
- break
- (parent, tail) = os.path.split(parent)
-
- defaults = {}
- option_list = {o.dest: o.type or type(o.default)
- for o in parser._actions}
-
- for section in ['pep8', 'pycodestyle', 'flake8']:
- if not config.has_section(section):
- continue
- for norm_opt, k, value in _get_normalize_options(
- args, config, section, option_list
- ):
- if args.verbose:
- print("enable config: section={}, key={}, value={}".format(
- section, k, value))
- defaults[norm_opt] = value
-
- parser.set_defaults(**defaults)
- except Error:
- # Ignore for now.
- pass
-
- return parser
-
-
- def read_pyproject_toml(args, parser):
- """Read pyproject.toml and load configuration."""
- if sys.version_info >= (3, 11):
- import tomllib
- else:
- import tomli as tomllib
-
- config = None
-
- if os.path.exists(args.global_config):
- with open(args.global_config, "rb") as fp:
- config = tomllib.load(fp)
-
- if not args.ignore_local_config:
- parent = tail = args.files and os.path.abspath(
- os.path.commonprefix(args.files))
- while tail:
- pyproject_toml = os.path.join(parent, "pyproject.toml")
- if os.path.exists(pyproject_toml):
- with open(pyproject_toml, "rb") as fp:
- config = tomllib.load(fp)
- break
- (parent, tail) = os.path.split(parent)
-
- if not config:
- return None
-
- if config.get("tool", {}).get("autopep8") is None:
- return None
-
- config = config.get("tool").get("autopep8")
-
- defaults = {}
- option_list = {o.dest: o.type or type(o.default)
- for o in parser._actions}
-
- TUPLED_OPTIONS = ("ignore", "select")
- for (k, v) in config.items():
- norm_opt = k.lstrip('-').replace('-', '_')
- if not option_list.get(norm_opt):
- continue
- if type(v) in (list, tuple) and norm_opt in TUPLED_OPTIONS:
- value = ",".join(v)
- else:
- value = v
- if args.verbose:
- print("enable pyproject.toml config: "
- "key={}, value={}".format(k, value))
- defaults[norm_opt] = value
-
- if defaults:
- # set value when exists key-value in defaults dict
- parser.set_defaults(**defaults)
-
- return parser
-
-
- def _split_comma_separated(string):
- """Return a set of strings."""
- return {text.strip() for text in string.split(',') if text.strip()}
-
-
- def decode_filename(filename):
- """Return Unicode filename."""
- if isinstance(filename, str):
- return filename
-
- return filename.decode(sys.getfilesystemencoding())
-
-
- def supported_fixes():
- """Yield pep8 error codes that autopep8 fixes.
-
- Each item we yield is a tuple of the code followed by its
- description.
-
- """
- yield ('E101', docstring_summary(reindent.__doc__))
-
- instance = FixPEP8(filename=None, options=None, contents='')
- for attribute in dir(instance):
- code = re.match('fix_([ew][0-9][0-9][0-9])', attribute)
- if code:
- yield (
- code.group(1).upper(),
- re.sub(r'\s+', ' ',
- docstring_summary(getattr(instance, attribute).__doc__))
- )
-
- for (code, function) in sorted(global_fixes()):
- yield (code.upper() + (4 - len(code)) * ' ',
- re.sub(r'\s+', ' ', docstring_summary(function.__doc__)))
-
- for code in sorted(CODE_TO_2TO3):
- yield (code.upper() + (4 - len(code)) * ' ',
- re.sub(r'\s+', ' ', docstring_summary(fix_2to3.__doc__)))
-
-
- def docstring_summary(docstring):
- """Return summary of docstring."""
- return docstring.split('\n')[0] if docstring else ''
-
-
- def line_shortening_rank(candidate, indent_word, max_line_length,
- experimental=False):
- """Return rank of candidate.
-
- This is for sorting candidates.
-
- """
- if not candidate.strip():
- return 0
-
- rank = 0
- lines = candidate.rstrip().split('\n')
-
- offset = 0
- if (
- not lines[0].lstrip().startswith('#') and
- lines[0].rstrip()[-1] not in '([{'
- ):
- for (opening, closing) in ('()', '[]', '{}'):
- # Don't penalize empty containers that aren't split up. Things like
- # this "foo(\n )" aren't particularly good.
- opening_loc = lines[0].find(opening)
- closing_loc = lines[0].find(closing)
- if opening_loc >= 0:
- if closing_loc < 0 or closing_loc != opening_loc + 1:
- offset = max(offset, 1 + opening_loc)
-
- current_longest = max(offset + len(x.strip()) for x in lines)
-
- rank += 4 * max(0, current_longest - max_line_length)
-
- rank += len(lines)
-
- # Too much variation in line length is ugly.
- rank += 2 * standard_deviation(len(line) for line in lines)
-
- bad_staring_symbol = {
- '(': ')',
- '[': ']',
- '{': '}'}.get(lines[0][-1])
-
- if len(lines) > 1:
- if (
- bad_staring_symbol and
- lines[1].lstrip().startswith(bad_staring_symbol)
- ):
- rank += 20
-
- for lineno, current_line in enumerate(lines):
- current_line = current_line.strip()
-
- if current_line.startswith('#'):
- continue
-
- for bad_start in ['.', '%', '+', '-', '/']:
- if current_line.startswith(bad_start):
- rank += 100
-
- # Do not tolerate operators on their own line.
- if current_line == bad_start:
- rank += 1000
-
- if (
- current_line.endswith(('.', '%', '+', '-', '/')) and
- "': " in current_line
- ):
- rank += 1000
-
- if current_line.endswith(('(', '[', '{', '.')):
- # Avoid lonely opening. They result in longer lines.
- if len(current_line) <= len(indent_word):
- rank += 100
-
- # Avoid the ugliness of ", (\n".
- if (
- current_line.endswith('(') and
- current_line[:-1].rstrip().endswith(',')
- ):
- rank += 100
-
- # Avoid the ugliness of "something[\n" and something[index][\n.
- if (
- current_line.endswith('[') and
- len(current_line) > 1 and
- (current_line[-2].isalnum() or current_line[-2] in ']')
- ):
- rank += 300
-
- # Also avoid the ugliness of "foo.\nbar"
- if current_line.endswith('.'):
- rank += 100
-
- if has_arithmetic_operator(current_line):
- rank += 100
-
- # Avoid breaking at unary operators.
- if re.match(r'.*[(\[{]\s*[\-\+~]$', current_line.rstrip('\\ ')):
- rank += 1000
-
- if re.match(r'.*lambda\s*\*$', current_line.rstrip('\\ ')):
- rank += 1000
-
- if current_line.endswith(('%', '(', '[', '{')):
- rank -= 20
-
- # Try to break list comprehensions at the "for".
- if current_line.startswith('for '):
- rank -= 50
-
- if current_line.endswith('\\'):
- # If a line ends in \-newline, it may be part of a
- # multiline string. In that case, we would like to know
- # how long that line is without the \-newline. If it's
- # longer than the maximum, or has comments, then we assume
- # that the \-newline is an okay candidate and only
- # penalize it a bit.
- total_len = len(current_line)
- lineno += 1
- while lineno < len(lines):
- total_len += len(lines[lineno])
-
- if lines[lineno].lstrip().startswith('#'):
- total_len = max_line_length
- break
-
- if not lines[lineno].endswith('\\'):
- break
-
- lineno += 1
-
- if total_len < max_line_length:
- rank += 10
- else:
- rank += 100 if experimental else 1
-
- # Prefer breaking at commas rather than colon.
- if ',' in current_line and current_line.endswith(':'):
- rank += 10
-
- # Avoid splitting dictionaries between key and value.
- if current_line.endswith(':'):
- rank += 100
-
- rank += 10 * count_unbalanced_brackets(current_line)
-
- return max(0, rank)
-
-
- def standard_deviation(numbers):
- """Return standard deviation."""
- numbers = list(numbers)
- if not numbers:
- return 0
- mean = sum(numbers) / len(numbers)
- return (sum((n - mean) ** 2 for n in numbers) /
- len(numbers)) ** .5
-
-
- def has_arithmetic_operator(line):
- """Return True if line contains any arithmetic operators."""
- for operator in pycodestyle.ARITHMETIC_OP:
- if operator in line:
- return True
-
- return False
-
-
- def count_unbalanced_brackets(line):
- """Return number of unmatched open/close brackets."""
- count = 0
- for opening, closing in ['()', '[]', '{}']:
- count += abs(line.count(opening) - line.count(closing))
-
- return count
-
-
- def split_at_offsets(line, offsets):
- """Split line at offsets.
-
- Return list of strings.
-
- """
- result = []
-
- previous_offset = 0
- current_offset = 0
- for current_offset in sorted(offsets):
- if current_offset < len(line) and previous_offset != current_offset:
- result.append(line[previous_offset:current_offset].strip())
- previous_offset = current_offset
-
- result.append(line[current_offset:])
-
- return result
-
-
- class LineEndingWrapper(object):
-
- r"""Replace line endings to work with sys.stdout.
-
- It seems that sys.stdout expects only '\n' as the line ending, no matter
- the platform. Otherwise, we get repeated line endings.
-
- """
-
- def __init__(self, output):
- self.__output = output
-
- def write(self, s):
- self.__output.write(s.replace('\r\n', '\n').replace('\r', '\n'))
-
- def flush(self):
- self.__output.flush()
-
-
- def match_file(filename, exclude):
- """Return True if file is okay for modifying/recursing."""
- base_name = os.path.basename(filename)
-
- if base_name.startswith('.'):
- return False
-
- for pattern in exclude:
- if fnmatch.fnmatch(base_name, pattern):
- return False
- if fnmatch.fnmatch(filename, pattern):
- return False
-
- if not os.path.isdir(filename) and not is_python_file(filename):
- return False
-
- return True
-
-
- def find_files(filenames, recursive, exclude):
- """Yield filenames."""
- while filenames:
- name = filenames.pop(0)
- if recursive and os.path.isdir(name):
- for root, directories, children in os.walk(name):
- filenames += [os.path.join(root, f) for f in children
- if match_file(os.path.join(root, f),
- exclude)]
- directories[:] = [d for d in directories
- if match_file(os.path.join(root, d),
- exclude)]
- else:
- is_exclude_match = False
- for pattern in exclude:
- if fnmatch.fnmatch(name, pattern):
- is_exclude_match = True
- break
- if not is_exclude_match:
- yield name
-
-
- def _fix_file(parameters):
- """Helper function for optionally running fix_file() in parallel."""
- if parameters[1].verbose:
- print('[file:{}]'.format(parameters[0]), file=sys.stderr)
- try:
- return fix_file(*parameters)
- except IOError as error:
- print(str(error), file=sys.stderr)
- raise error
-
-
- def fix_multiple_files(filenames, options, output=None):
- """Fix list of files.
-
- Optionally fix files recursively.
-
- """
- results = []
- filenames = find_files(filenames, options.recursive, options.exclude)
- if options.jobs > 1:
- import multiprocessing
- pool = multiprocessing.Pool(options.jobs)
- rets = []
- for name in filenames:
- ret = pool.apply_async(_fix_file, ((name, options),))
- rets.append(ret)
- pool.close()
- pool.join()
- if options.diff:
- for r in rets:
- sys.stdout.write(r.get().decode())
- sys.stdout.flush()
- results.extend([x.get() for x in rets if x is not None])
- else:
- for name in filenames:
- ret = _fix_file((name, options, output))
- if ret is None:
- continue
- if options.diff:
- if ret != '':
- results.append(ret)
- elif options.in_place:
- results.append(ret)
- else:
- original_source = readlines_from_file(name)
- if "".join(original_source).splitlines() != ret.splitlines():
- results.append(ret)
- return results
-
-
- def is_python_file(filename):
- """Return True if filename is Python file."""
- if filename.endswith('.py'):
- return True
-
- try:
- with open_with_encoding(
- filename,
- limit_byte_check=MAX_PYTHON_FILE_DETECTION_BYTES) as f:
- text = f.read(MAX_PYTHON_FILE_DETECTION_BYTES)
- if not text:
- return False
- first_line = text.splitlines()[0]
- except (IOError, IndexError):
- return False
-
- if not PYTHON_SHEBANG_REGEX.match(first_line):
- return False
-
- return True
-
-
- def is_probably_part_of_multiline(line):
- """Return True if line is likely part of a multiline string.
-
- When multiline strings are involved, pep8 reports the error as being
- at the start of the multiline string, which doesn't work for us.
-
- """
- return (
- '"""' in line or
- "'''" in line or
- line.rstrip().endswith('\\')
- )
-
-
- def wrap_output(output, encoding):
- """Return output with specified encoding."""
- return codecs.getwriter(encoding)(output.buffer
- if hasattr(output, 'buffer')
- else output)
-
-
- def get_encoding():
- """Return preferred encoding."""
- return locale.getpreferredencoding() or sys.getdefaultencoding()
-
-
- def main(argv=None, apply_config=True):
- """Command-line entry."""
- if argv is None:
- argv = sys.argv
-
- try:
- # Exit on broken pipe.
- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
- except AttributeError: # pragma: no cover
- # SIGPIPE is not available on Windows.
- pass
-
- try:
- args = parse_args(argv[1:], apply_config=apply_config)
-
- if args.list_fixes:
- for code, description in sorted(supported_fixes()):
- print('{code} - {description}'.format(
- code=code, description=description))
- return EXIT_CODE_OK
-
- if args.files == ['-']:
- assert not args.in_place
-
- encoding = sys.stdin.encoding or get_encoding()
- read_stdin = sys.stdin.read()
- fixed_stdin = fix_code(read_stdin, args, encoding=encoding)
-
- # LineEndingWrapper is unnecessary here due to the symmetry between
- # standard in and standard out.
- wrap_output(sys.stdout, encoding=encoding).write(fixed_stdin)
-
- if hash(read_stdin) != hash(fixed_stdin):
- if args.exit_code:
- return EXIT_CODE_EXISTS_DIFF
- else:
- if args.in_place or args.diff:
- args.files = list(set(args.files))
- else:
- assert len(args.files) == 1
- assert not args.recursive
-
- results = fix_multiple_files(args.files, args, sys.stdout)
- if args.diff:
- ret = any([len(ret) != 0 for ret in results])
- else:
- # with in-place option
- ret = any([ret is not None for ret in results])
- if args.exit_code and ret:
- return EXIT_CODE_EXISTS_DIFF
- except IOError:
- return EXIT_CODE_ERROR
- except KeyboardInterrupt:
- return EXIT_CODE_ERROR # pragma: no cover
-
-
- class CachedTokenizer(object):
-
- """A one-element cache around tokenize.generate_tokens().
-
- Original code written by Ned Batchelder, in coverage.py.
-
- """
-
- def __init__(self):
- self.last_text = None
- self.last_tokens = None
-
- def generate_tokens(self, text):
- """A stand-in for tokenize.generate_tokens()."""
- if text != self.last_text:
- string_io = io.StringIO(text)
- self.last_tokens = list(
- tokenize.generate_tokens(string_io.readline)
- )
- self.last_text = text
- return self.last_tokens
-
-
- _cached_tokenizer = CachedTokenizer()
- generate_tokens = _cached_tokenizer.generate_tokens
-
-
- if __name__ == '__main__':
- sys.exit(main())
|