| <html><head><meta charset="utf-8" lang="kotlin"> |
| |
| </head><body style="visibility: visible;" id="md"><meta charset="UTF-8"><meta http-equiv="content-type" content="text/html;charset=UTF-8"><meta name="viewport" content="width=600, initial-scale=1"><style>body{max-width:680px;margin:auto;padding:20px;text-align:justify;line-height:140%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;color:#222;font-family:Palatino,Georgia,"Times New Roman",serif}</style><style>@media print{*{-webkit-print-color-adjust:exact;text-shadow:none !important}}body{counter-reset: h1 paragraph line item list-item}@page{margin:0;size:auto}#mdContextMenu{position:absolute;background:#383838;cursor:default;border:1px solid #999;color:#fff;padding:4px 0px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,"Helvetica Neue",sans-serif;font-size:85%;font-weight:600;border-radius:4px;box-shadow:0px 3px 10px rgba(0,0,0,35%)}#mdContextMenu div{padding:0px 20px}#mdContextMenu div:hover{background:#1659d1}.md code,.md pre{font-family:Menlo,Consolas,monospace;font-size:85%;text-align:left;line-height:140%}.md .mediumToc code,.md longToc code,.md .shortToc code,.md h1 code,.md h2 code,.md h3 code,.md h4 code,.md h5 code,.md h6 code{font-size:unset}.md div.title{font-size:26px;font-weight:800;line-height:120%;text-align:center}.md div.afterTitles{height:10px}.md div.subtitle{text-align:center}.md iframe.textinsert, .md object.textinsert,.md iframe:not(.markdeep){display:block;margin-top:10px;margin-bottom:10px;width:100%;height:75vh;border:1px solid #000;border-radius:4px;background:#f5f5f4}.md .image{display:inline-block}.md img{max-width:100%;page-break-inside:avoid}.md li{text-align:left;text-indent:0}.md pre.listing {width:100%;tab-size:4;-moz-tab-size:4;-o-tab-size:4;counter-reset:line;overflow-x:auto;resize:horizontal}.md pre.listing .linenumbers span.line:before{width:30px;margin-left:-28px;font-size:80%;text-align:right;counter-increment:line;content:counter(line);display:inline-block;padding-right:13px;margin-right:8px;color:#ccc}.md div.tilde{margin:20px 0 -10px;text-align:center}.md .imagecaption,.md .tablecaption,.md .listingcaption{display:inline-block;margin:7px 5px 12px;text-align:justify;font-style:italic}.md img.pixel{image-rendering:-moz-crisp-edges;image-rendering:pixelated}.md blockquote.fancyquote{margin:25px 0 25px;text-align:left;line-height:160%}.md blockquote.fancyquote::before{content:"“";color:#DDD;font-family:Times New Roman;font-size:45px;line-height:0;margin-right:6px;vertical-align:-0.3em}.md span.fancyquote{font-size:118%;color:#777;font-style:italic}.md span.fancyquote::after{content:"”";font-style:normal;color:#DDD;font-family:Times New Roman;font-size:45px;line-height:0;margin-left:6px;vertical-align:-0.3em}.md blockquote.fancyquote .author{width:100%;margin-top:10px;display:inline-block;text-align:right}.md small{font-size:60%}.md big{font-size:150%}.md div.title,contents,.md .tocHeader,.md h1,.md h2,.md h3,.md h4,.md h5,.md h6,.md .shortTOC,.md .mediumTOC,.nonumberh1,.nonumberh2,.nonumberh3,.nonumberh4,.nonumberh5,.nonumberh6{font-family:Verdana,Helvetica,Arial,sans-serif;margin:13.4px 0 13.4px;padding:15px 0 3px;border-top:none;clear:both}.md .tocTop {display:none}.md h1,.md h2,.md h3,.md h4,.md h5,.md h6,.md .nonumberh1,.md .nonumberh2,.md .nonumberh3,.md .nonumberh4,.md .nonumberh5,.md .nonumberh6{page-break-after:avoid;break-after:avoid}.md svg.diagram{display:block;font-family:Menlo,Consolas,monospace;font-size:85%;text-align:center;stroke-linecap:round;stroke-width:2px;page-break-inside:avoid;stroke:#000;fill:#000}.md svg.diagram .opendot{fill:#fff}.md svg.diagram .shadeddot{fill:#CCC}.md svg.diagram .dotteddot{stroke:#000;stroke-dasharray:4;fill:none}.md svg.diagram text{stroke:none}@media print{@page{margin:1in 5mm;transform: scale(150%)}}@media print{.md .pagebreak{page-break-after:always;visibility:hidden}}.md a{font-family:Georgia,Palatino,'Times New Roman'}.md h1,.md .tocHeader,.md .nonumberh1{border-bottom:3px solid;font-size:20px;font-weight:bold;}.md h1,.md .nonumberh1{counter-reset:h2 h3 h4 h5 h6}.md h2,.md .nonumberh2{counter-reset:h3 h4 h5 h6;border-bottom:2px solid #999;color:#555;font-weight:bold;font-size:18px;}.md h3,.md h4,.md h5,.md h6,.md .nonumberh3,.md .nonumberh4,.md .nonumberh5,.md .nonumberh6{font-family:Verdana,Helvetica,Arial,sans-serif;color:#555;font-size:16px;}.md h3{counter-reset:h4 h5 h6}.md h4{counter-reset:h5 h6}.md h5{counter-reset:h6}.md div.table{margin:16px 0 16px 0}.md table{border-collapse:collapse;line-height:140%;page-break-inside:avoid}.md table.table{margin:auto}.md table.calendar{width:100%;margin:auto;font-size:11px;font-family:Verdana,Helvetica,Arial,sans-serif}.md table.calendar th{font-size:16px}.md .today{background:#ECF8FA}.md .calendar .parenthesized{color:#999;font-style:italic}.md table.table th{color:#FFF;background-color:#AAA;border:1px solid #888;padding:8px 15px 8px 15px}.md table.table td{padding:5px 15px 5px 15px;border:1px solid #888}.md table.table tr:nth-child(even){background:#EEE}.md pre.tilde{border-top: 1px solid #CCC;border-bottom: 1px solid #CCC;padding: 5px 0 5px 20px;margin:0 0 0 0;background:#FCFCFC;page-break-inside:avoid}.md a.target{width:0px;height:0px;visibility:hidden;font-size:0px;display:inline-block}.md a:link, .md a:visited{color:#38A;text-decoration:none}.md a:link:hover{text-decoration:underline}.md dt{font-weight:700}.md dl>dd{margin-top:-8px; margin-bottom:8px}.md dl>table{margin:35px 0 30px}.md code{page-break-inside:avoid;} @media print{.md .listing code{white-space:pre-wrap}}.md .endnote{font-size:13px;line-height:15px;padding-left:10px;text-indent:-10px}.md .bib{padding-left:80px;text-indent:-80px;text-align:left}.markdeepFooter{font-size:9px;text-align:right;padding-top:80px;color:#999}.md .mediumTOC{float:right;font-size:12px;line-height:15px;border-left:1px solid #CCC;padding-left:15px;margin:15px 0px 15px 25px}.md .mediumTOC .level1{font-weight:600}.md .longTOC .level1{font-weight:600;display:block;padding-top:12px;margin:0 0 -20px}.md .shortTOC{text-align:center;font-weight:bold;margin-top:15px;font-size:14px}.md .admonition{position:relative;margin:1em 0;padding:.4rem 1rem;border-radius:.2rem;border-left:2.5rem solid rgba(68,138,255,.4);background-color:rgba(68,138,255,.15);}.md .admonition-title{font-weight:bold;border-bottom:solid 1px rgba(68,138,255,.4);padding-bottom:4px;margin-bottom:4px;margin-left: -1rem;padding-left:1rem;margin-right:-1rem;border-color:rgba(68,138,255,.4)}.md .admonition.tip{border-left:2.5rem solid rgba(50,255,90,.4);background-color:rgba(50,255,90,.15)}.md .admonition.tip::before{content:"\24d8";font-weight:bold;font-size:150%;position:relative;top:3px;color:rgba(26,128,46,.8);left:-2.95rem;display:block;width:0;height:0}.md .admonition.tip>.admonition-title{border-color:rgba(50,255,90,.4)}.md .admonition.warn,.md .admonition.warning{border-left:2.5rem solid rgba(255,145,0,.4);background-color:rgba(255,145,0,.15)}.md .admonition.warn::before,.md .admonition.warning::before{content:"\26A0";font-weight:bold;font-size:150%;position:relative;top:2px;color:rgba(128,73,0,.8);left:-2.95rem;display:block;width:0;height:0}.md .admonition.warn>.admonition-title,.md .admonition.warning>.admonition-title{border-color:rgba(255,145,0,.4)}.md .admonition.error{border-left: 2.5rem solid rgba(255,23,68,.4);background-color:rgba(255,23,68,.15)}.md .admonition.error>.admonition-title{border-color:rgba(255,23,68,.4)}.md .admonition.error::before{content: "\2612";font-family:"Arial";font-size:200%;position:relative;color:rgba(128,12,34,.8);top:-2px;left:-3rem;display:block;width:0;height:0}.md .admonition p:last-child{margin-bottom:0}.md li.checked,.md li.unchecked{list-style:none;overflow:visible;text-indent:-1.2em}.md li.checked:before,.md li.unchecked:before{content:"\2611";display:block;float:left;width:1em;font-size:120%}.md li.unchecked:before{content:"\2610"}</style><style>.md h1::before { |
| content:counter(h1) " "; |
| counter-increment: h1;margin-right:10px} |
| |
| .md h2::before { |
| content:counter(h1) "."counter(h2) " "; |
| counter-increment: h2;margin-right:10px} |
| |
| .md h3::before { |
| content:counter(h1) "."counter(h2) "."counter(h3) " "; |
| counter-increment: h3;margin-right:10px} |
| |
| .md h4::before { |
| content:counter(h1) "."counter(h2) "."counter(h3) "."counter(h4) " "; |
| counter-increment: h4;margin-right:10px} |
| |
| .md h5::before { |
| content:counter(h1) "."counter(h2) "."counter(h3) "."counter(h4) "."counter(h5) " "; |
| counter-increment: h5;margin-right:10px} |
| |
| .md h6::before { |
| content:counter(h1) "."counter(h2) "."counter(h3) "."counter(h4) "."counter(h5) "."counter(h6) " "; |
| counter-increment: h6;margin-right:10px} |
| |
| </style><style>.hljs{display:block;overflow-x:auto;padding:0.5em;background:#fff;color:#000;-webkit-text-size-adjust:none}.hljs-comment{color:#006a00}.hljs-keyword{color:#02E}.hljs-literal,.nginx .hljs-title{color:#aa0d91}.method,.hljs-list .hljs-title,.hljs-tag .hljs-title,.setting .hljs-value,.hljs-winutils,.tex .hljs-command,.http .hljs-title,.hljs-request,.hljs-status,.hljs-name{color:#008}.hljs-envvar,.tex .hljs-special{color:#660}.hljs-string{color:#c41a16}.hljs-tag .hljs-value,.hljs-cdata,.hljs-filter .hljs-argument,.hljs-attr_selector,.apache .hljs-cbracket,.hljs-date,.hljs-regexp{color:#080}.hljs-sub .hljs-identifier,.hljs-pi,.hljs-tag,.hljs-tag .hljs-keyword,.hljs-decorator,.ini .hljs-title,.hljs-shebang,.hljs-prompt,.hljs-hexcolor,.hljs-rule .hljs-value,.hljs-symbol,.hljs-symbol .hljs-string,.hljs-number,.css .hljs-function,.hljs-function .hljs-title,.coffeescript .hljs-attribute{color:#A0C}.hljs-function .hljs-title{font-weight:bold;color:#000}.hljs-class .hljs-title,.smalltalk .hljs-class,.hljs-type,.hljs-typename,.hljs-tag .hljs-attribute,.hljs-doctype,.hljs-class .hljs-id,.hljs-built_in,.setting,.hljs-params,.clojure .hljs-attribute{color:#5c2699}.hljs-variable{color:#3f6e74}.css .hljs-tag,.hljs-rule .hljs-property,.hljs-pseudo,.hljs-subst{color:#000}.css .hljs-class,.css .hljs-id{color:#9b703f}.hljs-value .hljs-important{color:#ff7700;font-weight:bold}.hljs-rule .hljs-keyword{color:#c5af75}.hljs-annotation,.apache .hljs-sqbracket,.nginx .hljs-built_in{color:#9b859d}.hljs-preprocessor,.hljs-preprocessor *,.hljs-pragma{color:#643820}.tex .hljs-formula{background-color:#eee;font-style:italic}.diff .hljs-header,.hljs-chunk{color:#808080;font-weight:bold}.diff .hljs-change{background-color:#bccff9}.hljs-addition{background-color:#baeeba}.hljs-deletion{background-color:#ffc8bd}.hljs-comment .hljs-doctag{font-weight:bold}.method .hljs-id{color:#000}</style><style>div.title { padding-top: 40px; } div.afterTitles { height: 15px; }</style><meta charset="utf-8" lang="kotlin"> |
| |
| <span class="md"><p><title>Android Lint API Guide</title></p><div class="title"> Android Lint API Guide </div> |
| |
| <div class="afterTitles"></div> |
| |
| <p></p><p> |
| |
| This chapter inlines all the API documentation into a single |
| long book, suitable for printing or reading on a tablet. |
| |
| </p> |
| <div class="longTOC"><div class="tocHeader">Contents</div><p><a href="#" class="tocTop">(Top)</a><br> |
| <a href="#terminology" class="level1"><span class="tocNumber">1 </span>Terminology</a><br> |
| <a href="#writingalintcheck:basics" class="level1"><span class="tocNumber">2 </span>Writing a Lint Check: Basics</a><br> |
| <a href="#writingalintcheck:basics/preliminaries" class="level2"><span class="tocNumber">2.1 </span>Preliminaries</a><br> |
| <a href="#writingalintcheck:basics/preliminaries/%E2%80%9Clint?%E2%80%9D" class="level3"><span class="tocNumber">2.1.1 </span>“Lint?”</a><br> |
| <a href="#writingalintcheck:basics/preliminaries/apistability" class="level3"><span class="tocNumber">2.1.2 </span>API Stability</a><br> |
| <a href="#writingalintcheck:basics/preliminaries/kotlin" class="level3"><span class="tocNumber">2.1.3 </span>Kotlin</a><br> |
| <a href="#writingalintcheck:basics/concepts" class="level2"><span class="tocNumber">2.2 </span>Concepts</a><br> |
| <a href="#writingalintcheck:basics/clientapiversusdetectorapi" class="level2"><span class="tocNumber">2.3 </span>Client API versus Detector API</a><br> |
| <a href="#writingalintcheck:basics/creatinganissue" class="level2"><span class="tocNumber">2.4 </span>Creating an Issue</a><br> |
| <a href="#writingalintcheck:basics/textformat" class="level2"><span class="tocNumber">2.5 </span>TextFormat</a><br> |
| <a href="#writingalintcheck:basics/issueimplementation" class="level2"><span class="tocNumber">2.6 </span>Issue Implementation</a><br> |
| <a href="#writingalintcheck:basics/scopes" class="level2"><span class="tocNumber">2.7 </span>Scopes</a><br> |
| <a href="#writingalintcheck:basics/registeringtheissue" class="level2"><span class="tocNumber">2.8 </span>Registering the Issue</a><br> |
| <a href="#writingalintcheck:basics/implementingadetector:scanners" class="level2"><span class="tocNumber">2.9 </span>Implementing a Detector: Scanners</a><br> |
| <a href="#writingalintcheck:basics/detectorlifecycle" class="level2"><span class="tocNumber">2.10 </span>Detector Lifecycle</a><br> |
| <a href="#writingalintcheck:basics/scannerorder" class="level2"><span class="tocNumber">2.11 </span>Scanner Order</a><br> |
| <a href="#writingalintcheck:basics/implementingadetector:services" class="level2"><span class="tocNumber">2.12 </span>Implementing a Detector: Services</a><br> |
| <a href="#writingalintcheck:basics/scannerexample" class="level2"><span class="tocNumber">2.13 </span>Scanner Example</a><br> |
| <a href="#writingalintcheck:basics/analyzingkotlinandjavacode" class="level2"><span class="tocNumber">2.14 </span>Analyzing Kotlin and Java Code</a><br> |
| <a href="#writingalintcheck:basics/analyzingkotlinandjavacode/uast" class="level3"><span class="tocNumber">2.14.1 </span>UAST</a><br> |
| <a href="#writingalintcheck:basics/analyzingkotlinandjavacode/uastexample" class="level3"><span class="tocNumber">2.14.2 </span>UAST Example</a><br> |
| <a href="#writingalintcheck:basics/analyzingkotlinandjavacode/lookingupuast" class="level3"><span class="tocNumber">2.14.3 </span>Looking up UAST</a><br> |
| <a href="#writingalintcheck:basics/analyzingkotlinandjavacode/resolving" class="level3"><span class="tocNumber">2.14.4 </span>Resolving</a><br> |
| <a href="#writingalintcheck:basics/analyzingkotlinandjavacode/psi" class="level3"><span class="tocNumber">2.14.5 </span>PSI</a><br> |
| <a href="#writingalintcheck:basics/testing" class="level2"><span class="tocNumber">2.15 </span>Testing</a><br> |
| <a href="#example:samplelintcheckgithubproject" class="level1"><span class="tocNumber">3 </span>Example: Sample Lint Check GitHub Project</a><br> |
| <a href="#example:samplelintcheckgithubproject/projectlayout" class="level2"><span class="tocNumber">3.1 </span>Project Layout</a><br> |
| <a href="#example:samplelintcheckgithubproject/:checks" class="level2"><span class="tocNumber">3.2 </span>:checks</a><br> |
| <a href="#example:samplelintcheckgithubproject/lintversion?" class="level2"><span class="tocNumber">3.3 </span>lintVersion?</a><br> |
| <a href="#example:samplelintcheckgithubproject/:libraryand:app" class="level2"><span class="tocNumber">3.4 </span>:library and :app</a><br> |
| <a href="#example:samplelintcheckgithubproject/lintcheckprojectlayout" class="level2"><span class="tocNumber">3.5 </span>Lint Check Project Layout</a><br> |
| <a href="#example:samplelintcheckgithubproject/serviceregistration" class="level2"><span class="tocNumber">3.6 </span>Service Registration</a><br> |
| <a href="#example:samplelintcheckgithubproject/issueregistry" class="level2"><span class="tocNumber">3.7 </span>IssueRegistry</a><br> |
| <a href="#example:samplelintcheckgithubproject/detector" class="level2"><span class="tocNumber">3.8 </span>Detector</a><br> |
| <a href="#example:samplelintcheckgithubproject/detectortest" class="level2"><span class="tocNumber">3.9 </span>Detector Test</a><br> |
| <a href="#publishingalintcheck" class="level1"><span class="tocNumber">4 </span>Publishing a Lint Check</a><br> |
| <a href="#publishingalintcheck/android" class="level2"><span class="tocNumber">4.1 </span>Android</a><br> |
| <a href="#publishingalintcheck/android/aarsupport" class="level3"><span class="tocNumber">4.1.1 </span>AAR Support</a><br> |
| <a href="#publishingalintcheck/android/lintpublishconfiguration" class="level3"><span class="tocNumber">4.1.2 </span>lintPublish Configuration</a><br> |
| <a href="#publishingalintcheck/android/localchecks" class="level3"><span class="tocNumber">4.1.3 </span>Local Checks</a><br> |
| <a href="#publishingalintcheck/android/unpublishing" class="level3"><span class="tocNumber">4.1.4 </span>Unpublishing</a><br> |
| <a href="#lintcheckunittesting" class="level1"><span class="tocNumber">5 </span>Lint Check Unit Testing</a><br> |
| <a href="#lintcheckunittesting/creatingaunittest" class="level2"><span class="tocNumber">5.1 </span>Creating a Unit Test</a><br> |
| <a href="#lintcheckunittesting/computingtheexpectedoutput" class="level2"><span class="tocNumber">5.2 </span>Computing the Expected Output</a><br> |
| <a href="#lintcheckunittesting/testfiles" class="level2"><span class="tocNumber">5.3 </span>Test Files</a><br> |
| <a href="#lintcheckunittesting/trimmingindents?" class="level2"><span class="tocNumber">5.4 </span>Trimming indents?</a><br> |
| <a href="#lintcheckunittesting/dollarsinrawstrings" class="level2"><span class="tocNumber">5.5 </span>Dollars in Raw Strings</a><br> |
| <a href="#lintcheckunittesting/quickfixes" class="level2"><span class="tocNumber">5.6 </span>Quickfixes</a><br> |
| <a href="#lintcheckunittesting/librarydependenciesandstubs" class="level2"><span class="tocNumber">5.7 </span>Library Dependencies and Stubs</a><br> |
| <a href="#lintcheckunittesting/binaryandcompiledsourcefiles" class="level2"><span class="tocNumber">5.8 </span>Binary and Compiled Source Files</a><br> |
| <a href="#lintcheckunittesting/mydetectorisn'tinvokedfromatest!" class="level2"><span class="tocNumber">5.9 </span>My Detector Isn't Invoked From a Test!</a><br> |
| <a href="#addingquickfixes" class="level1"><span class="tocNumber">6 </span>Adding Quick Fixes</a><br> |
| <a href="#addingquickfixes/introduction" class="level2"><span class="tocNumber">6.1 </span>Introduction</a><br> |
| <a href="#addingquickfixes/thelintfixbuilderclass" class="level2"><span class="tocNumber">6.2 </span>The LintFix builder class</a><br> |
| <a href="#addingquickfixes/creatingalintfix" class="level2"><span class="tocNumber">6.3 </span>Creating a LintFix</a><br> |
| <a href="#addingquickfixes/availablefixes" class="level2"><span class="tocNumber">6.4 </span>Available Fixes</a><br> |
| <a href="#addingquickfixes/combiningfixes" class="level2"><span class="tocNumber">6.5 </span>Combining Fixes</a><br> |
| <a href="#addingquickfixes/refactoringjavaandkotlincode" class="level2"><span class="tocNumber">6.6 </span>Refactoring Java and Kotlin code</a><br> |
| <a href="#addingquickfixes/regularexpressionsandbackreferences" class="level2"><span class="tocNumber">6.7 </span>Regular Expressions and Back References</a><br> |
| <a href="#addingquickfixes/emittingquickfixxmltoapplyonci" class="level2"><span class="tocNumber">6.8 </span>Emitting quick fix XML to apply on CI</a><br> |
| <a href="#partialanalysis" class="level1"><span class="tocNumber">7 </span>Partial Analysis</a><br> |
| <a href="#partialanalysis/about" class="level2"><span class="tocNumber">7.1 </span>About</a><br> |
| <a href="#partialanalysis/theproblem" class="level2"><span class="tocNumber">7.2 </span>The Problem</a><br> |
| <a href="#partialanalysis/overview" class="level2"><span class="tocNumber">7.3 </span>Overview</a><br> |
| <a href="#partialanalysis/doesmydetectorneedwork?" class="level2"><span class="tocNumber">7.4 </span>Does My Detector Need Work?</a><br> |
| <a href="#partialanalysis/doesmydetectorneedwork?/catchingmistakes:blockingaccesstomainproject" class="level3"><span class="tocNumber">7.4.1 </span>Catching Mistakes: Blocking Access to Main Project</a><br> |
| <a href="#partialanalysis/doesmydetectorneedwork?/catchingmistakes:simulatedappmodule" class="level3"><span class="tocNumber">7.4.2 </span>Catching Mistakes: Simulated App Module</a><br> |
| <a href="#partialanalysis/doesmydetectorneedwork?/catchingmistakes:diffingresults" class="level3"><span class="tocNumber">7.4.3 </span>Catching Mistakes: Diffing Results</a><br> |
| <a href="#partialanalysis/doesmydetectorneedwork?/catchingmistakes:remainingissues" class="level3"><span class="tocNumber">7.4.4 </span>Catching Mistakes: Remaining Issues</a><br> |
| <a href="#partialanalysis/incidents" class="level2"><span class="tocNumber">7.5 </span>Incidents</a><br> |
| <a href="#partialanalysis/constraints" class="level2"><span class="tocNumber">7.6 </span>Constraints</a><br> |
| <a href="#partialanalysis/incidentlintmaps" class="level2"><span class="tocNumber">7.7 </span>Incident LintMaps</a><br> |
| <a href="#partialanalysis/modulelintmaps" class="level2"><span class="tocNumber">7.8 </span>Module LintMaps</a><br> |
| <a href="#partialanalysis/optimizations" class="level2"><span class="tocNumber">7.9 </span>Optimizations</a><br> |
| <a href="#frequentlyaskedquestions" class="level1"><span class="tocNumber">8 </span>Frequently Asked Questions</a><br> |
| <a href="#frequentlyaskedquestions//mydetectorcallbacksaren'tinvoked" class="level3"><span class="tocNumber">8.0.1 </span>My detector callbacks aren't invoked</a><br> |
| <a href="#frequentlyaskedquestions//mylintcheckworksfromtheunittestbutnotintheide" class="level3"><span class="tocNumber">8.0.2 </span>My lint check works from the unit test but not in the IDE</a><br> |
| <a href="#frequentlyaskedquestions//visitannotationusageisn'tcalledforannotations" class="level3"><span class="tocNumber">8.0.3 </span><code>visitAnnotationUsage</code> isn't called for annotations</a><br> |
| <a href="#frequentlyaskedquestions//howdoicheckifauastorpsielementisforjavaorkotlin?" class="level3"><span class="tocNumber">8.0.4 </span>How do I check if a UAST or PSI element is for Java or Kotlin?</a><br> |
| <a href="#frequentlyaskedquestions//whatifineedapsielementandihaveauelement?" class="level3"><span class="tocNumber">8.0.5 </span>What if I need a <code>PsiElement</code> and I have a <code>UElement</code> ?</a><br> |
| <a href="#frequentlyaskedquestions//howdoigettheumethodforapsimethod?" class="level3"><span class="tocNumber">8.0.6 </span>How do I get the <code>UMethod</code> for a <code>PsiMethod</code> ?</a><br> |
| <a href="#frequentlyaskedquestions//howdogetajavaevaluator?" class="level3"><span class="tocNumber">8.0.7 </span>How do get a <code>JavaEvaluator</code> ?</a><br> |
| <a href="#frequentlyaskedquestions//howdoicheckwhetheranelementisinternal?" class="level3"><span class="tocNumber">8.0.8 </span>How do I check whether an element is internal?</a><br> |
| <a href="#frequentlyaskedquestions//iselementinline,sealed,operator,infix,suspend,data?" class="level3"><span class="tocNumber">8.0.9 </span>Is element inline, sealed, operator, infix, suspend, data?</a><br> |
| <a href="#frequentlyaskedquestions//howdoilookupaclassifihaveitsfullyqualifiedname?" class="level3"><span class="tocNumber">8.0.10 </span>How do I look up a class if I have its fully qualified name?</a><br> |
| <a href="#frequentlyaskedquestions//howdoilookupaclassifihaveapsitype?" class="level3"><span class="tocNumber">8.0.11 </span>How do I look up a class if I have a PsiType?</a><br> |
| <a href="#frequentlyaskedquestions//howdoilookuphierarhcyannotationsforanelement?" class="level3"><span class="tocNumber">8.0.12 </span>How do I look up hierarhcy annotations for an element?</a><br> |
| <a href="#frequentlyaskedquestions//howdoilookupifaclassisasubclassofanother?" class="level3"><span class="tocNumber">8.0.13 </span>How do I look up if a class is a subclass of another?</a><br> |
| <a href="#frequentlyaskedquestions//howdoiknowwhichparameteracallargumentcorrespondsto?" class="level3"><span class="tocNumber">8.0.14 </span>How do I know which parameter a call argument corresponds to?</a><br> |
| <a href="#frequentlyaskedquestions//howcanmylintcheckstargettwodifferentversionsoflint?" class="level3"><span class="tocNumber">8.0.15 </span>How can my lint checks target two different versions of lint?</a><br> |
| <a href="#frequentlyaskedquestions//howdoicheckoutthecurrentlintsourcecode?" class="level3"><span class="tocNumber">8.0.16 </span>How do I check out the current lint source code?</a><br> |
| <a href="#frequentlyaskedquestions//wheredoifindexamplesoflintchecks?" class="level3"><span class="tocNumber">8.0.17 </span>Where do I find examples of lint checks?</a><br> |
| <a href="#appendix:recentchanges" class="level1"><span class="tocNumber">9 </span>Appendix: Recent Changes</a><br> |
| <a href="#appendix:environmentvariablesandsystemproperties" class="level1"><span class="tocNumber">10 </span>Appendix: Environment Variables and System Properties</a><br> |
| <a href="#appendix:environmentvariablesandsystemproperties/environmentvariables" class="level2"><span class="tocNumber">10.1 </span>Environment Variables</a><br> |
| <a href="#appendix:environmentvariablesandsystemproperties/environmentvariables/detectorconfigurationvariables" class="level3"><span class="tocNumber">10.1.1 </span>Detector Configuration Variables</a><br> |
| <a href="#appendix:environmentvariablesandsystemproperties/environmentvariables/lintconfigurationvariables" class="level3"><span class="tocNumber">10.1.2 </span>Lint Configuration Variables</a><br> |
| <a href="#appendix:environmentvariablesandsystemproperties/environmentvariables/lintdevelopmentvariables" class="level3"><span class="tocNumber">10.1.3 </span>Lint Development Variables</a><br> |
| <a href="#appendix:environmentvariablesandsystemproperties/systemproperties" class="level2"><span class="tocNumber">10.2 </span>System Properties</a><br> |
| </p></div><a class="target" name="terminology"> </a><a class="target" name="terminology"> </a><a class="target" name="toc1"> </a><h1>Terminology</h1> |
| <p> |
| |
| |
| You don't need to read this up front and understand everything, but |
| this is hopefully a handy reference to return to. |
| |
| </p><p> |
| |
| In alphabetical order: |
| |
| </p><p> |
| |
| </p><dl><dt>Configuration</dt><dd><p> A configuration provides extra information or parameters to lint on a |
| per project, or even per directory basis. For example, the <code>lint.xml</code> |
| files can change the severity for issues, or list incidents to ignore |
| (matched for example by a regular expression), or even provide values |
| for options read by a specific detector. |
| |
| </p></dd><dt>Context</dt><dd><p> An object passed into detectors in many APIs, providing data about |
| (for example) which file is being analyzed (and in which project), |
| and for specific types of analysis additional information; for |
| example, an XmlContext points to the DOM document, a JavaContext |
| includes the AST, and so on. |
| |
| </p></dd><dt>Detector</dt><dd><p> The implementation of the lint check which registers Issues, analyzes |
| the code, and reports Incidents. |
| |
| </p></dd><dt>Implementation</dt><dd><p> An <code>Implementation</code> tells lint how a given issue is actually |
| analyzed, such as which detector class to instantiate, as well as |
| which scopes the detector applies to. |
| |
| </p></dd><dt>Incident</dt><dd><p> A specific occurrence of the issue at a specific location. |
| An example of an incident is: |
| </p><pre class="listing backtick"><code><span class="line"> Warning: In file IoUtils.kt, line 140, the field download folder</span> |
| <span class="line"> is "/sdcard/downloads"; do not hardcode the path to `/sdcard`.</span></code></pre></dd><dt>Issue</dt><dd><p> A type or class of problem that your lint check identifies. An issue |
| has an associated severity (error, warning or info), a priority, a |
| category, an explanation, and so on. |
| |
| </p><p> |
| |
| An example of an issue is “Don't hardcode paths to /sdcard”. |
| |
| </p></dd><dt>IssueRegistry</dt><dd><p> An <code>IssueRegistry</code> provides a list of issues to lint. When you write |
| one or more lint checks, you'll register these in an <code>IssueRegistry</code> |
| and point to it using the <code>META-INF</code> service loader mechanism. |
| |
| </p></dd><dt>LintClient</dt><dd><p> The <code>LintClient</code> represents the specific tool the detector is running |
| in. For example, when running in the IDE there is a LintClient which |
| (when incidents are reported) will show highlights in the editor, |
| whereas when lint is running as part of the Gradle plugin, incidents |
| are instead accumulated into HTML (and XML and text) reports, and |
| the build interrupted on error. |
| |
| </p></dd><dt>Location</dt><dd><p> A “location” refers to a place where an incident is reported. |
| Typically this refers to a text range within a source file, but a |
| location can also point to a binary file such as a <code>png</code> file. |
| Locations can also be linked together, along with descriptions. |
| Therefore, if you for example are reporting a duplicate declaration, |
| you can include <strong class="asterisk">both</strong> Locations, and in the IDE, both locations |
| (if they're in the same file) will be highlighted. A location linked |
| from another is called a “secondary” location, but the chaining can |
| be as long as you want (and lint's unit testing infrastructure will |
| make sure there are no cycles.) |
| |
| </p></dd><dt>Partial Analysis</dt><dd><p> A “map reduce” architecture in lint which makes it possible to |
| analyze individual modules in isolation and then later filter and |
| customize the partial results based on conditions outside of these |
| modules. This is explained in greater detail in the |
| <a href="#partialanalysis">partial analysis</a> chapter. |
| |
| </p></dd><dt>Platform</dt><dd><p> The <code>Platform</code> abstraction allows lint issues to indicate where they |
| apply (such as “Android”, or “Server”, and so on). This means that an |
| Android-specific check won't trigger warnings on non-Android code. |
| |
| </p></dd><dt>Scanner</dt><dd><p> A <code>Scanner</code> is a particular interface a detector can implement to |
| indicate that it supports a specific set of callbacks. For example, |
| the <code>XmlScanner</code> interface is where the methods for visiting XML |
| elements and attributes are defined, and the <code>ClassScanner</code> is where |
| the ASM bytecode handling methods are defined, and so on. |
| |
| </p></dd><dt>Scope</dt><dd><p> <code>Scope</code> is an enum which lists various types of files that a detector |
| may want to analyze. |
| |
| </p><p> |
| |
| For example, there is a scope for XML files, there is a scope for |
| Java and Kotlin files, there is a scope for .class files, and so on. |
| |
| </p><p> |
| |
| Typically lint cares about which <strong class="asterisk">set</strong> of scopes apply, |
| so most of the APIs take an <code>EnumSet< Scope></code>, but we'll often |
| refer to this as just “the scope” instead of the “scope set”. |
| |
| </p></dd><dt>Severity</dt><dd><p> For an issue, whether the incident should be an error, or just a |
| warning, or neither (just an FYI highlight). There is also a special |
| type of error severity, “fatal”, discussed later. |
| |
| </p></dd><dt>TextFormat</dt><dd><p> An enum describing various text formats lint understands. Lint checks |
| will typically only operate with the “raw” format, which is |
| markdown-like (e.g. you can surround words with an asterisk to make |
| it italics or two to make it bold, and so on). |
| |
| </p></dd><dt>Vendor</dt><dd><p> A <code>Vendor</code> is a simple data class which provides information about |
| the provenance of a lint check: who wrote it, where to file issues, |
| and so on. |
| |
| </p></dd></dl><p></p> |
| <a class="target" name="writingalintcheck:basics"> </a><a class="target" name="writingalintcheck:basics"> </a><a class="target" name="toc2"> </a><h1>Writing a Lint Check: Basics</h1> |
| |
| <a class="target" name="preliminaries"> </a><a class="target" name="writingalintcheck:basics/preliminaries"> </a><a class="target" name="toc2.1"> </a><h2>Preliminaries</h2> |
| <p> |
| |
| |
| (If you already know a lot of the basics but you're here because you've |
| run into a problem and you're consulting the docs, take a look at the |
| <a href="#frequentlyaskedquestions">frequently asked questions</a> chapter.) |
| |
| </p> |
| <a class="target" name="%E2%80%9Clint?%E2%80%9D"> </a><a class="target" name="writingalintcheck:basics/preliminaries/%E2%80%9Clint?%E2%80%9D"> </a><a class="target" name="toc2.1.1"> </a><h3>“Lint?”</h3> |
| <p> |
| |
| |
| The <code>lint</code> tool shipped with the C compiler and provided additional |
| static analysis of C code beyond what the compiler checked. |
| |
| </p><p> |
| |
| Android Lint was named in honor of this tool, and with the Android |
| prefix to make it really clear that this is a static analysis tool |
| intended for analysis of Android code, provided by the Android Open |
| Source Project — and to disambiguate it from the many other tools with |
| “lint“ in their names. |
| |
| </p><p> |
| |
| However, since then, Android Lint has broadened its support and is no |
| longer intended only for Android code. In fact, within Google, it is |
| used to analyze all Java and Kotlin code. One of the reasons for this |
| is that it can easily analyze both Java and Kotlin code without having |
| to implement the checks twice. Additional features are described in the |
| <a href="api-guide/../features.html.md">features</a> chapter. |
| |
| </p><p> |
| |
| We're planning to rename lint to reflect this new role, so we are |
| looking for good name suggestions. |
| |
| </p> |
| <a class="target" name="apistability"> </a><a class="target" name="writingalintcheck:basics/preliminaries/apistability"> </a><a class="target" name="toc2.1.2"> </a><h3>API Stability</h3> |
| <p> |
| |
| |
| Lint's APIs are still marked as @Beta, and we have made it very clear |
| all along that this is not a stable API, so custom lint checks may need |
| to be updated periodically to keep working. |
| |
| </p><p> |
| |
| However, ”some APIs are more stable than others“. In particular, the |
| detector API (described below) is much less likely to change than the |
| client API (which is not intended for lint check authors but for tools |
| integrating lint to run within, such as IDEs and build systems). |
| |
| </p><p> |
| |
| However, this doesn't mean the detector API won't change. A large part |
| of the API surface is external to lint; it's the AST libraries (PSI and |
| UAST) for Java and Kotlin from JetBrains; it's the bytecode library |
| (asm.ow2.io), it's the XML DOM library (org.w3c.dom), and so on. Lint |
| intentionally stays up to date with these, so any API or behavior |
| changes in these can affect your lint checks. |
| |
| </p><p> |
| |
| Lint's own APIs may also change. The current API has grown organically |
| over the last 10 years (the first version of lint was released in 2011) |
| and there are a number of things we'd clean up and do differently if |
| starting over. Not to mention rename and clean up inconsistencies. |
| |
| </p><p> |
| |
| However, lint has been pretty widely adopted, so at this point creating |
| a nicer API would probably cause more harm than good, so we're limiting |
| recent changes to just the necessary ones. An example of this is the |
| new <a href="#partialanalysis">partial analysis</a> architecture in 7.0 |
| which is there to allow much better CI and incremental analysis |
| performance. |
| |
| </p> |
| <a class="target" name="kotlin"> </a><a class="target" name="writingalintcheck:basics/preliminaries/kotlin"> </a><a class="target" name="toc2.1.3"> </a><h3>Kotlin</h3> |
| <p> |
| |
| |
| We recommend that you implement your checks in Kotlin. Part of |
| the reason for that is that the lint API uses a number of Kotlin |
| features: |
| |
| </p><p> |
| |
| </p><ul> |
| <li class="asterisk"><strong class="asterisk">Named and default parameters</strong>: Rather than using builders, some |
| construction methods, like <code>Issue.create()</code> have a lot of parameters |
| with default parameters. The API is cleaner to use if you just |
| specify what you need and rely on defaults for everything else. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk"><strong class="asterisk">Compatibility</strong>: We may add additional parameters over time. It |
| isn't practical to add @JvmOverloads on everything. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk"><strong class="asterisk">Package-level functions</strong>: Lint's API includes a number of package |
| level utility functions (in previous versions of the API these are all |
| thrown together in a <code>LintUtils</code> class). |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk"><strong class="asterisk">Deprecations</strong>: Kotlin has support for simple API migrations. For |
| example, in the below example, the new <code>@Deprecated</code> annotation on |
| lines 1 through 7 will be added in an upcoming release, to ease |
| migration to a new API. IntelliJ can automatically quickfix these |
| deprecation replacements.</li></ul> |
| |
| <p></p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line"><span class="hljs-meta">@Deprecated(</span> |
| <span class="line"> <span class="hljs-meta-string">"Use the new report(Incident) method instead, which is more future proof"</span>,</span> |
| <span class="line"> ReplaceWith(</span> |
| <span class="line"> <span class="hljs-meta-string">"report(Incident(issue, message, location, null, quickfixData))"</span>,</span> |
| <span class="line"> <span class="hljs-meta-string">"com.android.tools.lint.detector.api.Incident"</span></span> |
| <span class="line"> )</span></span> |
| <span class="line">)</span> |
| <span class="line"><span class="hljs-meta">@JvmOverloads</span></span> |
| <span class="line"><span class="hljs-keyword">open</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">report</span><span class="hljs-params">(</span> |
| <span class="line"> issue: <span class="hljs-type">Issue</span>,</span> |
| <span class="line"> location: <span class="hljs-type">Location</span>,</span> |
| <span class="line"> message: <span class="hljs-type">String</span>,</span> |
| <span class="line"> quickfixData: <span class="hljs-type">LintFix</span>? = <span class="hljs-literal">null</span></span> |
| <span class="line">)</span></span> {</span> |
| <span class="line"> <span class="hljs-comment">// ...</span></span> |
| <span class="line">}</span></div></code></pre><p> |
| |
| As of 7.0, there is more Kotlin code in lint than remaining Java |
| code: |
| </p><div class="table"> |
| <table class="table"><tbody><tr><th style="text-align:left"> Language </th><th style="text-align:right"> files </th><th style="text-align:right"> blank </th><th style="text-align:right"> comment </th><th style="text-align:right"> code </th></tr> |
| <tr><td style="text-align:left"> Kotlin </td><td style="text-align:right"> 420 </td><td style="text-align:right"> 14243 </td><td style="text-align:right"> 23239 </td><td style="text-align:right"> 130250 </td></tr> |
| <tr><td style="text-align:left"> Java </td><td style="text-align:right"> 289 </td><td style="text-align:right"> 8683 </td><td style="text-align:right"> 15205 </td><td style="text-align:right"> 101549 </td></tr> |
| </tbody></table><center><div class="tablecaption"><code>$ cloc lint/</code></div></center></div> |
| |
| <p></p><p> |
| |
| And that's for all of lint, including many old lint detectors which |
| haven't been touched in years. In the Lint API library, |
| <code>lint/libs/lint-api</code>, the code is 78% Kotlin and 22% Java. |
| |
| </p> |
| <a class="target" name="concepts"> </a><a class="target" name="writingalintcheck:basics/concepts"> </a><a class="target" name="toc2.2"> </a><h2>Concepts</h2> |
| <p> |
| |
| |
| Lint will search your source code for problems. There are many types of |
| problems, and each one is called an <code>Issue</code>, which has associated |
| metadata like a unique id, a category, an explanation, and so on. |
| |
| </p><p> |
| |
| Each instance that it finds is called an ”incident“. |
| |
| </p><p> |
| |
| The actual responsibility of searching for and reporting incidents is |
| handled by detectors — subclasses of <code>Detector</code>. Your lint check will |
| extend <code>Detector</code>, and when it has found a problem, it will ”report“ |
| the incident to lint. |
| |
| </p><p> |
| |
| A <code>Detector</code> can analyze more than one <code>Issue</code>. For example, the |
| built-in <code>StringFormatDetector</code> analyzes formatting strings passed to |
| <code>String.format()</code> calls, and in the process of doing that discovers |
| multiple unrelated issues — invalid formatting strings, formatting |
| strings which should probably use the plurals API instead, mismatched |
| types, and so on. The detector could simply have a single issue called |
| “StringFormatProblems” and report everything as a StringFormatProblem, |
| but that's not a good idea. Each of these individual types of String |
| format problems should have their own explanation, their own category, |
| their own severity, and most importantly should be individually |
| configurable by the user such that they can disable or promote one of |
| these issues separately from the others. |
| |
| </p><p> |
| |
| A <code>Detector</code> can indicate which sets of files it cares about. These are |
| called “scopes”, and the way this works is that when you register your |
| <code>Issue</code>, you tell that issue which <code>Detector</code> class is responsible for |
| analyzing it, as well as which scopes the detector cares about. |
| |
| </p><p> |
| |
| If for example a lint check wants to analyze Kotlin files, it can |
| include the <code>Scope.JAVA_FILE</code> scope, and now that detector will be |
| included when lint processes Java or Kotin files. |
| |
| </p><p> |
| |
| </p><div class="admonition tip">The name <code>Scope.JAVA_FILE</code> may make it sound like there should also |
| be a <code>Scope.KOTLIN_FILE</code>. However, <code>JAVA_FILE</code> here really refers to |
| both Java and Kotlin files since the analysis and APIs are identical |
| for both (using “UAST”, a universal abstract syntax tree). However, |
| at this point we don't want to rename it since it would break a lot |
| of existing checks. We might introduce an alias and deprecate this |
| one in the future.</div> |
| |
| <p></p><p> |
| |
| When detectors implement various callbacks, they can analyze the |
| code, and if they find a problematic pattern, they can “report” |
| the incident. This means computing an error message, as well as |
| a “location”. A “location” for an incident is really an error |
| range — a file, and a starting offset and an ending offset. Locations |
| can also be linked together, so for example for a “duplicate |
| declaration” error, you can and should include both locations. |
| |
| </p><p> |
| |
| Many detector methods will pass in a <code>Context</code>, or a more specific |
| subclass of <code>Context</code> such as <code>JavaContext</code> or <code>XmlContext</code>. This |
| allows lint to provide access to the detectors information they may |
| need, without passing in a lot of parameters (and allowing lint to add |
| additional data over time without breaking signatures). |
| |
| </p><p> |
| |
| The <code>Context</code> classes also provide many convenience APIs. For example, |
| for <code>XmlContext</code> there are methods for creating locations for XML tags, |
| XML attributes, just the name part of an XML attribute and just the |
| value part of an XML attribute. For a <code>JavaContext</code> there are also |
| methods for creating locations, such as for a method call, including |
| whether to include the receiver and/or the argument list. |
| |
| </p><p> |
| |
| When you report an <code>Incident</code> you can also provide a <code>LintFix</code>; this is |
| a quickfix which the IDE can use to offer actions to take on the |
| warning. In some cases, you can offer a complete and correct fix (such |
| as removing an unused element). In other cases the fix may be less |
| clear; for example, the <code>AccessibilityDetector</code> asks you to set a |
| description for images; the quickfix will set the content attribute, |
| but will leave the text value as TODO and will select the string such |
| that the user can just type to replace it. |
| |
| </p><p> |
| |
| </p><div class="admonition tip">When reporting incidents, make sure that the error messages are not |
| generic; try to be explicit and include specifics for the current |
| scenario. For example, instead of just “Duplicate declaration”, use |
| “<code>$name</code> has already been declared”. This isn't just for cosmetics; |
| it also makes lint's <a href="#baselines">baseline |
| mechanism</a> work better since it |
| currently matches by id + file + message, not by line numbers which |
| typically drift over time.</div> |
| |
| <p></p> |
| <a class="target" name="clientapiversusdetectorapi"> </a><a class="target" name="writingalintcheck:basics/clientapiversusdetectorapi"> </a><a class="target" name="toc2.3"> </a><h2>Client API versus Detector API</h2> |
| <p> |
| |
| |
| Lint's API has two halves: |
| |
| </p><p> |
| |
| </p><ul> |
| <li class="minus">The <strong class="asterisk">Client API</strong>: “Integrate (and run) lint from within a tool”. |
| For example, both the IDE and the build system uses this API to embed |
| and invoke lint to analyze the code in the project or editor. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="minus">The <strong class="asterisk">Detector API</strong>: “Implement a new lint check”. This is the API |
| which lets checkers analyze code and report problems that they find.</li></ul> |
| |
| <p></p><p> |
| |
| The class in the Client API which represents lint running in a tool is |
| called <code>LintClient</code>. This class is responsible for, among other things: |
| |
| </p><p> |
| |
| </p><ul> |
| <li class="asterisk">Reporting incidents found by detectors. For example, in the IDE, it |
| will place error markers into the source editor, and in a build |
| system, it may write warnings to the console or generate a report or |
| even fail the build. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk">Handling I/O. Detectors should never read files from disk directly. |
| This allows lint checks to work smoothly in for example the IDE. When |
| lint runs on the fly, and a lint check asks for the source file |
| contents (or other supporting files), the <code>LintClient</code> in the IDE |
| will implement the <code>readFile</code> method to first look in the open source |
| editors and if the requested file is being edited, it will return the |
| current (often unsaved!) contents. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk">Handling network traffic. Lint checks should never open |
| URLConnections themselves. By going through the lint API to request |
| data for a URL, not only can the LintClient for example use any |
| configured IDE proxy settings which is done in the IntelliJ |
| integration of lint, but even the lint check's own unit tests can |
| easily be tested because the special unit test implementation of a |
| <code>LintClient</code> provides a simple way to provide exact responses for |
| specific URLs:</li></ul> |
| |
| <p></p><pre class="listing tilde"><code><span class="line">lint()</span> |
| <span class="line"> .files(...)</span> |
| <span class="line"> // Set up exactly the expected maven.google.com network output to</span> |
| <span class="line"> // ensure stable version suggestions in the tests</span> |
| <span class="line"> .networkData("https://maven.google.com/master-index.xml", ""</span> |
| <span class="line"> + "<span class="hljs-comment"><!--?xml version='1.0' encoding='UTF-8'?--></span>\n"</span> |
| <span class="line"> + "<span class="hljs-tag"><<span class="hljs-name">metadata</span>></span>\n"</span> |
| <span class="line"> + " <span class="hljs-tag"><<span class="hljs-name">com.android.tools.build</span>></span>"</span> |
| <span class="line"> + "<span class="hljs-tag"></<span class="hljs-name">com.android.tools.build</span>></span><span class="hljs-tag"></<span class="hljs-name">metadata</span>></span>")</span> |
| <span class="line"> .networkData("https://maven.google.com/com/android/tools/build/group-index.xml", ""</span> |
| <span class="line"> + "<span class="hljs-comment"><!--?xml version='1.0' encoding='UTF-8'?--></span>\n"</span> |
| <span class="line"> + "<span class="hljs-tag"><<span class="hljs-name">com.android.tools.build</span>></span>\n"</span> |
| <span class="line"> + " <span class="hljs-tag"><<span class="hljs-name">gradle</span> <span class="hljs-attr">versions</span>=<span class="hljs-string">"\"</span><span class="hljs-attr">2.3.3</span>,<span class="hljs-attr">3.0.0-alpha1</span>\"/"></span>\n"</span> |
| <span class="line"> + "<span class="hljs-tag"></<span class="hljs-name">gradle</span>></span><span class="hljs-tag"></<span class="hljs-name">com.android.tools.build</span>></span>")</span> |
| <span class="line">.run()</span> |
| <span class="line">.expect(...)</span></code></pre><p> |
| |
| And much, much, more. <strong class="asterisk">However, most of the implementation of |
| <code>LintClient</code> is intended for integration of lint itself, and as a check |
| author you don't need to worry about it.</strong> It's the detector API that |
| matters, and is also less likely to change than the client API. |
| |
| </p><p> |
| |
| </p><div class="admonition tip">The division between the two halves is not perfect; some classes |
| do not fit neatly in between the two or historically were put in |
| the wrong place, so this is a high level design to be aware of but |
| which is not absolute.</div> |
| |
| <p></p><p> |
| |
| Also, |
| |
| </p><p> |
| |
| </p><div class="admonition warning">Because of the division between two separate packages, which in |
| retrospect was a mistake, a number of APIs that are only intended |
| for internal lint usage have been made <code>public</code> such that lint's |
| code in one package can access it from the other. There's normally a |
| comment explaining that this is for internal use only, but be aware |
| that just because something is <code>public</code> or not <code>final</code> it's a good |
| idea to call or override it.</div> |
| |
| <p></p> |
| <a class="target" name="creatinganissue"> </a><a class="target" name="writingalintcheck:basics/creatinganissue"> </a><a class="target" name="toc2.4"> </a><h2>Creating an Issue</h2> |
| <p> |
| |
| |
| For information on how to set up the project and to actually publish |
| your lint checks, see the <a href="#example:samplelintcheckgithubproject">sample</a> and |
| <a href="#publishingalintcheck">publishing</a> chapters. |
| |
| </p><p> |
| |
| <code>Issue</code> is a final class, so unlike <code>Detector</code>, you don't subclass |
| it, you instantiate it via <code>Issue.create</code>. |
| |
| </p><p> |
| |
| By convention, issues are registered inside the companion object of the |
| corresponding detector, but that is not required. |
| |
| </p><p> |
| |
| Here's an example: |
| |
| </p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SdCardDetector</span> : <span class="hljs-type">Detector</span></span>(), SourceCodeScanner {</span> |
| <span class="line"> <span class="hljs-keyword">companion</span> <span class="hljs-keyword">object</span> Issues {</span> |
| <span class="line"> <span class="hljs-meta">@JvmField</span></span> |
| <span class="line"> <span class="hljs-keyword">val</span> ISSUE = Issue.create(</span> |
| <span class="line"> id = <span class="hljs-string">"SdCardPath"</span>,</span> |
| <span class="line"> briefDescription = <span class="hljs-string">"Hardcoded reference to `/sdcard`"</span>,</span> |
| <span class="line"> explanation = <span class="hljs-string">"""</span> |
| <span class="line"> Your code should not reference the `/sdcard` path directly; \</span> |
| <span class="line"> instead use `Environment.getExternalStorageDirectory().getPath()`.</span> |
| <span class="line"></span> |
| <span class="line"> Similarly, do not reference the `/data/data/` path directly; it \</span> |
| <span class="line"> can vary in multi-user scenarios. Instead, use \</span> |
| <span class="line"> `Context.getFilesDir().getPath()`.</span> |
| <span class="line"> """</span>,</span> |
| <span class="line"> moreInfo = <span class="hljs-string">"https://developer.android.com/training/data-storage#filesExternal"</span>,</span> |
| <span class="line"> category = Category.CORRECTNESS,</span> |
| <span class="line"> severity = Severity.WARNING,</span> |
| <span class="line"> androidSpecific = <span class="hljs-literal">true</span>,</span> |
| <span class="line"> implementation = Implementation(</span> |
| <span class="line"> SdCardDetector::<span class="hljs-keyword">class</span>.java,</span> |
| <span class="line"> Scope.JAVA_FILE_SCOPE</span> |
| <span class="line"> )</span> |
| <span class="line"> )</span> |
| <span class="line"> }</span> |
| <span class="line"> ...</span></div></code></pre><p> |
| |
| There are a number of things to note here. |
| |
| </p><p> |
| |
| On line 4, we have the <code>Issue.create()</code> call. We store the issue into a |
| property such that we can reference this issue both from the |
| <code>IssueRegistry</code>, where we provide the <code>Issue</code> to lint, and also in the |
| <code>Detector</code> code where we report incidents of the issue. |
| |
| </p><p> |
| |
| Note that <code>Issue.create</code> is a method with a lot of parameters (and we |
| will probably add more parameters in the future). Therefore, it's a |
| good practice to explicitly include the argument names (and therefore |
| to implement your code in Kotlin). |
| |
| </p><p> |
| |
| The <code>Issue</code> provides metadata about a type of problem. |
| |
| </p><p> |
| |
| The <strong class="asterisk"><code>id</code></strong> is a short, unique identifier for this issue. By |
| convention it is a combination of words, capitalized camel case (though |
| you can also add your own package prefix as in Java packages). Note |
| that the id is “user visible”; it is included in text output when lint |
| runs in the build system, such as this: |
| |
| </p><pre class="listing backtick"><code><span class="line">src/main/kotlin/test/pkg/MyTest.kt:4: Warning: Do not hardcode "/sdcard/";</span> |
| <span class="line"> use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]</span> |
| <span class="line"> val s: String = "/sdcard/mydir"</span> |
| <span class="line"> ~~~~~~~~~~~~~</span> |
| <span class="line">0 errors, 1 warnings</span></code></pre><p> |
| |
| (Notice the <code>[SdCardPath]</code> suffix at the end of the error message.) |
| |
| </p><p> |
| |
| The reason the id is made known to the user is that the ID is how |
| they'll configure and/or suppress issues. For example, to suppress the |
| warning in the current method, use |
| |
| </p><pre class="listing backtick"><code><span class="line"><span class="hljs-meta">@Suppress(<span class="hljs-params"><span class="hljs-string">"SdCardPath"</span></span>)</span></span></code></pre><p> |
| |
| (or in Java, @SuppressWarnings). Note that there is an IDE quickfix to |
| suppress an incident which will automatically add these annotations, so |
| you don't need to know the ID in order to be able to suppress an |
| incident, but the ID will be visible in the annotation that it |
| generates, so it should be reasonably specific. |
| |
| </p><p> |
| |
| Also, since the namespace is global, try to avoid picking generic names |
| that could clash with others, or seem to cover a larger set of issues |
| than intended. For example, “InvalidDeclaration” would be a poor id |
| since that can cover a lot of potential problems with declarations |
| across a number of languages and technologies. |
| |
| </p><p> |
| |
| Next, we have the <strong class="asterisk"><code>briefDescription</code></strong>. You can think of this as a |
| “category report header“; this is a static description for all |
| incidents of this type, so it cannot include any specifics. This string |
| is used for example as a header in HTML reports for all incidents of |
| this type, and in the IDE, if you open the Inspections UI, the various |
| issues are listed there using the brief descriptions. |
| |
| </p><p> |
| |
| The <strong class="asterisk"><code>explanation</code></strong> is a multi line, ideally multi-paragraph |
| explanation of what the problem is. In some cases, the problem is self |
| evident, as in the case of ”Unused declaration“, but in many cases, the |
| issue is more subtle and might require additional explanation, |
| particularly for what the developer should <strong class="asterisk">do</strong> to address the |
| problem. The explanation is included both in HTML reports and in the |
| IDE inspection results window. |
| |
| </p><p> |
| |
| Note that even though we're using a raw string, and even though the |
| string is indented to be flush with the rest of the issue registration |
| for better readability, we don't need to call <code>trimIndent()</code> on |
| the raw string. Lint does that automatically. |
| |
| </p><p> |
| |
| However, we do need to add line continuations — those are the trailing |
| \'s at the end of the lines. |
| |
| </p><p> |
| |
| Note also that we have a Markdown-like simple syntax, described in the |
| “TextFormat” section below. You can use asterisks for italics or double |
| asterisks for bold, you can use apostrophes for code font, and so on. |
| In terminal output this doesn't make a difference, but the IDE, |
| explanations, incident error messages, etc, are all formatted using |
| these styles. |
| |
| </p><p> |
| |
| The <strong class="asterisk"><code>category</code></strong> isn't super important; the main use is that category |
| names can be treated as id's when it comes to issue configuration; for |
| example, a user can turn off all internationalization issues, or run |
| lint against only the security related issues. The category is also |
| used for locating related issues in HTML reports. If none of the |
| built-in categories are appropriate you can also create your own. |
| |
| </p><p> |
| |
| The <strong class="asterisk"><code>severity</code></strong> property is very important. An issue can be either a |
| warning or an error. These are treated differently in the IDE (where |
| errors are red underlines and warnings are yellow highlights), and in |
| the build system (where errors can optionally break the build and |
| warnings do not). There are some other severities too; ”fatal“ is like |
| error except these checks are designated important enough (and have |
| very few false positives) such that we run them during release builds, |
| even if the user hasn't explicitly run a lint target. There's also |
| “informational” severity, which is only used in one or two places, and |
| finally the “ignore” severity. This is never the severity you register |
| for an issue, but it's part of the severities a developer can configure |
| for a particular issue, thereby turning off that particular check. |
| |
| </p><p> |
| |
| You can also specify a <strong class="asterisk"><code>moreInfo</code></strong> URL which will be included in the |
| issue explanation as a “More Info” link to open to read more details |
| about this issue or underlying problem. |
| |
| </p> |
| <a class="target" name="textformat"> </a><a class="target" name="writingalintcheck:basics/textformat"> </a><a class="target" name="toc2.5"> </a><h2>TextFormat</h2> |
| <p> |
| |
| |
| All error messages and issue metadata strings in lint are interpreted |
| using simple Markdown-like syntax: |
| </p><div class="table"> |
| <table class="table"><tbody><tr><th style="text-align:left"> Raw text format </th><th style="text-align:left"> Renders To </th></tr> |
| <tr><td style="text-align:left"> This is a `code symbol` </td><td style="text-align:left"> This is a <code>code symbol</code> </td></tr> |
| <tr><td style="text-align:left"> This is <code>*italics*</code> </td><td style="text-align:left"> This is <em class="asterisk">italics</em> </td></tr> |
| <tr><td style="text-align:left"> This is <code>**bold**</code> </td><td style="text-align:left"> This is <strong class="asterisk">bold</strong> </td></tr> |
| <tr><td style="text-align:left"> <a href="http://," class="url">http://,</a> <a href="https:// " class="url">https:// </a></td><td style="text-align:left"> <a href="http://"></a><a href="http://</a" class="url">http://, </a><a href="https://"></a><a href="https://</a" class="url">https:// </a></td></tr> |
| <tr><td style="text-align:left"> <code>\*not italics*</code> </td><td style="text-align:left"> <code>\*not italics*</code> </td></tr> |
| <tr><td style="text-align:left"> ```language\n text\n``` </td><td style="text-align:left"> (preformatted text block) </td></tr> |
| </tbody></table><center><div class="tablecaption">Supported markup in lint's markdown-like raw text format</div></center></div> |
| |
| <p></p><p> |
| |
| This is useful when error messages and issue explanations are shown in |
| HTML reports generated by Lint, or in the IDE, where for example the |
| error message tooltips will use formatting. |
| |
| </p><p> |
| |
| In the API, there is a <code>TextFormat</code> enum which encapsulates the |
| different text formats, and the above syntax is referred to as |
| <code>TextFormat.RAW</code>; it can be converted to <code>.TEXT</code> or <code>.HTML</code> for |
| example, which lint does when writing text reports to the console or |
| HTML reports to files respectively. As a lint check author you don't |
| need to know this (though you can for example with the unit testing |
| support decide which format you want to compare against in your |
| expected output), but the main point here is that your issue's brief |
| description, issue explanation, incident report messages etc, should |
| use the above “raw” syntax. Especially the first conversion; error |
| messages often refer to class names and method names, and these should |
| be surrounded by apostrophes. |
| |
| </p> |
| <a class="target" name="issueimplementation"> </a><a class="target" name="writingalintcheck:basics/issueimplementation"> </a><a class="target" name="toc2.6"> </a><h2>Issue Implementation</h2> |
| <p> |
| |
| |
| The last issue registration property is the <strong class="asterisk"><code>implementation</code></strong>. This |
| is where we glue our metadata to our specific implementation of an |
| analyzer which can find instances of this issue. |
| |
| </p><p> |
| |
| Normally, the <code>Implementation</code> provides two things: |
| |
| </p><p> |
| |
| </p><ul> |
| <li class="asterisk">The <code>.class</code> for our <code>Detector</code> which should be instantiated. In the |
| code sample above it was <code>SdCardDetector</code>. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk">The <code>Scope</code> that this issue's detector applies to. In the above |
| example it was <code>Scope.JAVA_FILE</code>, which means it will apply to Java |
| and Kotlin files.</li></ul> |
| |
| <p></p> |
| <a class="target" name="scopes"> </a><a class="target" name="writingalintcheck:basics/scopes"> </a><a class="target" name="toc2.7"> </a><h2>Scopes</h2> |
| <p> |
| |
| |
| The <code>Implementation</code> actually takes a <strong class="asterisk">set</strong> of scopes; we still refer |
| to this as a “scope”. Some lint checks want to analyze multiple types |
| of files. For example, the <code>StringFormatDetector</code> will analyze both the |
| resource files declaring the formatting strings across various locales, |
| as well as the Java and Kotlin files containing <code>String.format</code> calls |
| referencing the formatting strings. |
| |
| </p><p> |
| |
| There are a number of pre-defined sets of scopes in the <code>Scope</code> |
| class. <code>Scope.JAVA_FILE_SCOPE</code> is the most common, which is a |
| singleton set containing exactly <code>Scope.JAVA_FILE</code>, but you |
| can always create your own, such as for example |
| </p><pre class="listing backtick"><code><span class="line"> <span class="hljs-selector-tag">EnumSet</span><span class="hljs-selector-class">.of</span>(Scope.CLASS_FILE, Scope.JAVA_LIBRARIES)</span></code></pre><p> |
| |
| When a lint issue requires multiple scopes, that means lint will |
| <strong class="asterisk">only</strong> run this detector if <strong class="asterisk">all</strong> the scopes are available in the |
| running tool. When lint runs a full batch run (such as a Gradle lint |
| target or a full “Inspect Code“ in the IDE), all scopes are available. |
| |
| </p><p> |
| |
| However, when lint runs on the fly in the editor, it only has access to |
| the current file; it won't re-analyze <em class="asterisk">all</em> files in the project for |
| every few keystrokes. So in this case, the scope in the lint driver |
| only includes the current source file's type, and only lint checks |
| which specify a scope that is a subset would run. |
| |
| </p><p> |
| |
| This is a common mistake for new lint check authors: the lint check |
| works just fine as a unit test, but they don't see working in the IDE |
| because the issue implementation requests multiple scopes, and <strong class="asterisk">all</strong> |
| have to be available. |
| |
| </p><p> |
| |
| Often, a lint check looks at multiple source file types to work |
| correctly in all cases, but it can still identify <em class="asterisk">some</em> problems given |
| individual source files. In this case, the <code>Implementation</code> constructor |
| (which takes a vararg of scope sets) can be handed additional sets of |
| scopes, called ”analysis scopes“. If the current lint client's scope |
| matches or is a subset of any of the analysis scopes, then the check |
| will run after all. |
| |
| </p> |
| <a class="target" name="registeringtheissue"> </a><a class="target" name="writingalintcheck:basics/registeringtheissue"> </a><a class="target" name="toc2.8"> </a><h2>Registering the Issue</h2> |
| <p> |
| |
| |
| Once you've created your issue, you need to provide it from |
| an <code>IssueRegistry</code>. |
| |
| </p><p> |
| |
| Here's an example <code>IssueRegistry</code>: |
| |
| </p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line"><span class="hljs-keyword">package</span> com.example.lint.checks</span> |
| <span class="line"></span> |
| <span class="line"><span class="hljs-keyword">import</span> com.android.tools.lint.client.api.IssueRegistry</span> |
| <span class="line"><span class="hljs-keyword">import</span> com.android.tools.lint.client.api.Vendor</span> |
| <span class="line"><span class="hljs-keyword">import</span> com.android.tools.lint.detector.api.CURRENT_API</span> |
| <span class="line"></span> |
| <span class="line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SampleIssueRegistry</span> : <span class="hljs-type">IssueRegistry</span></span>() {</span> |
| <span class="line"> <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> issues = listOf(SdCardDetector.ISSUE)</span> |
| <span class="line"></span> |
| <span class="line"> <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> api: <span class="hljs-built_in">Int</span></span> |
| <span class="line"> <span class="hljs-keyword">get</span>() = CURRENT_API</span> |
| <span class="line"></span> |
| <span class="line"> <span class="hljs-comment">// works with Studio 4.1 or later; see</span></span> |
| <span class="line"> <span class="hljs-comment">// com.android.tools.lint.detector.api.Api / ApiKt</span></span> |
| <span class="line"> <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> minApi: <span class="hljs-built_in">Int</span></span> |
| <span class="line"> <span class="hljs-keyword">get</span>() = <span class="hljs-number">8</span></span> |
| <span class="line"></span> |
| <span class="line"> <span class="hljs-comment">// Requires lint API 30.0+; if you're still building for something</span></span> |
| <span class="line"> <span class="hljs-comment">// older, just remove this property.</span></span> |
| <span class="line"> <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> vendor: Vendor = Vendor(</span> |
| <span class="line"> vendorName = <span class="hljs-string">"Android Open Source Project"</span>,</span> |
| <span class="line"> feedbackUrl = <span class="hljs-string">"https://com.example.lint.blah.blah"</span>,</span> |
| <span class="line"> contact = <span class="hljs-string">"author@com.example.lint"</span></span> |
| <span class="line"> )</span> |
| <span class="line">}</span></div></code></pre><p> |
| |
| On line 8, we're returning our issue. It's a list, so an |
| <code>IssueRegistry</code> can provide multiple issues. |
| |
| </p><p> |
| |
| The <strong class="asterisk"><code>api</code></strong> property should be written exactly like the way it |
| appears above in your own issue registry as well; this will record |
| which version of the lint API this issue registry was compiled against |
| (because this references a static final constant which will be copied |
| into the jar file instead of looked up dynamically when the jar is |
| loaded). |
| |
| </p><p> |
| |
| The <strong class="asterisk"><code>minApi</code></strong> property records the oldest lint API level this check |
| has been tested with. |
| |
| </p><p> |
| |
| Both of these are used at issue loading time to make sure lint checks |
| are compatible, but in recent versions of lint (7.0) lint will more |
| aggressively try to load older detectors even if they have been |
| compiled against older APIs since there's a high likelihood that they |
| will work (it checks all the lint APIs in the bytecode and uses |
| reflection to verify that they're still there). |
| |
| </p><p> |
| |
| The <strong class="asterisk"><code>vendor</code></strong> property is new as of 7.0, and gives lint authors a |
| way to indicate where the lint check came from. When users use lint, |
| they're running hundreds and hundreds of checks, and sometimes it's not |
| clear who to contact with requests or bug reports. When a vendor has |
| been specified, lint will include this information in error output and |
| reports. |
| |
| </p><p> |
| |
| The last step towards making the lint check available is to make |
| the <code>IssueRegistry</code> known via the service loader mechanism. |
| |
| </p><p> |
| |
| Create a file named exactly |
| </p><pre class="listing backtick"><code><span class="line"><span class="hljs-selector-tag">src</span>/<span class="hljs-selector-tag">main</span>/<span class="hljs-selector-tag">resources</span>/<span class="hljs-selector-tag">META-INF</span>/<span class="hljs-selector-tag">services</span>/<span class="hljs-selector-tag">com</span><span class="hljs-selector-class">.android</span><span class="hljs-selector-class">.tools</span><span class="hljs-selector-class">.lint</span><span class="hljs-selector-class">.client</span><span class="hljs-selector-class">.api</span><span class="hljs-selector-class">.IssueRegistry</span></span></code></pre><p> |
| |
| with the following contents (but where you substitute in your own |
| fully qualified class name for your issue registry): |
| |
| </p><pre class="listing backtick"><code><span class="line"><span class="hljs-selector-tag">com</span><span class="hljs-selector-class">.example</span><span class="hljs-selector-class">.lint</span><span class="hljs-selector-class">.checks</span><span class="hljs-selector-class">.SampleIssueRegistry</span></span></code></pre><p> |
| |
| If you're not building your lint check using Gradle, you may not want |
| the <code>src/main/resources</code> prefix; the point is that your packaging of |
| the jar file should contain <code>META-INF/services/</code> at the root of the jar |
| file. |
| |
| </p> |
| <a class="target" name="implementingadetector:scanners"> </a><a class="target" name="writingalintcheck:basics/implementingadetector:scanners"> </a><a class="target" name="toc2.9"> </a><h2>Implementing a Detector: Scanners</h2> |
| <p> |
| |
| |
| We've finally come to the main task with writing a lint check: |
| implementing the <strong class="asterisk"><code>Detector</code></strong>. |
| |
| </p><p> |
| |
| Here's a trivial one: |
| </p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyDetector</span> : <span class="hljs-type">Detector</span></span>() {</span> |
| <span class="line"> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">run</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>)</span></span> {</span> |
| <span class="line"> context.report(ISSUE, Location.create(context.file),</span> |
| <span class="line"> <span class="hljs-string">"I complain a lot"</span>)</span> |
| <span class="line"> }</span> |
| <span class="line">}</span></div></code></pre><p> |
| |
| This will just complain in every single file. Obviously, no real lint |
| detector does this; we want to do some analysis and <strong class="asterisk">conditionally</strong> |
| report incidents. |
| |
| </p><p> |
| |
| In order to make it simpler to perform analysis, Lint has dedicated |
| support for analyzing various file types. The way this works is that |
| you register interest, and then various callbacks will be invoked. |
| |
| </p><p> |
| |
| For example: |
| |
| </p><p> |
| |
| </p><ul> |
| <li class="asterisk">When implementing <strong class="asterisk"><code>XmlScanner</code></strong>, in an XML element you can be |
| called back |
| <ul> |
| <li class="minus">when any of a set of given tags are declared (<code>visitElement</code>) |
| </li> |
| <li class="minus">when any of a set of named attributes are declared |
| (<code>visitAttribute</code>) |
| </li> |
| <li class="minus">and you can perform your own document traversal via <code>visitDocument</code> |
| |
| <p></p><p> |
| |
| </p></li></ul> |
| </li><li class="asterisk">When implementing <strong class="asterisk"><code>SourceCodeScanner</code></strong>, in Kotlin and Java files |
| you can be called back |
| <ul> |
| <li class="minus">When a method of a given name is invoked (<code>getApplicableMethodNames</code> |
| and <code>visitMethodCall</code>) |
| </li> |
| <li class="minus">When a class of the given type is instantiated |
| (<code>getApplicableConstructorTypes</code> and <code>visitConstructor</code>) |
| </li> |
| <li class="minus">When a new class is declared which extends (possibly indirectly) |
| a given class or interface (<code>applicableSuperClasses</code> and |
| <code>visitClass</code>) |
| </li> |
| <li class="minus">When annotated elements are referenced or combined |
| (<code>applicableAnnotations</code> and <code>visitAnnotationUsage</code>) |
| </li> |
| <li class="minus">When any AST nodes of given types appear (<code>getApplicableUastTypes</code> |
| and <code>createUastHandler</code>) |
| |
| <p></p><p> |
| |
| </p></li></ul> |
| </li><li class="asterisk">When implementing a <strong class="asterisk"><code>ClassScanner</code></strong>, in <code>.class</code> and <code>.jar</code> files |
| you can be called back |
| <ul> |
| <li class="minus">when a method is invoked for a particular owner |
| (<code>getApplicableCallOwners</code> and <code>checkCall</code> |
| </li> |
| <li class="minus">when a given bytecode instruction occurs |
| (<code>getApplicableAsmNodeTypes</code> and <code>checkInstruction</code>) |
| </li> |
| <li class="minus">like with XmlScanner's <code>visitDocument</code>, you can perform your own |
| ASM bytecode iteration via <code>checkClass</code>. |
| |
| <p></p><p> |
| |
| </p></li></ul> |
| </li><li class="asterisk">There are various other scanners too, for example <code>GradleScanner</code> |
| which lets you visit <code>build.gradle</code> and <code>build.gradle.kts</code> DSL |
| closures, <code>BinaryFileScanner</code> which visits resource files such as |
| webp and png files, and <code>OtherFileScanner</code> which lets you visit |
| unknown files.</li></ul> |
| |
| <p></p><p> |
| |
| </p><div class="admonition note">Note that <code>Detector</code> already implements empty stub methods for all |
| of these interfaces, so if you for example implement |
| <code>SourceFileScanner</code> in your detector, you don't need to go and add |
| empty implementations for all the methods you aren't using.</div> |
| |
| <p></p><p> |
| |
| </p><div class="admonition tip">None of Lint's APIs require you to call <code>super</code> when you override |
| methods; methods meant to be overridden are always empty so the |
| super-call is superfluous.</div> |
| |
| <p></p> |
| <a class="target" name="detectorlifecycle"> </a><a class="target" name="writingalintcheck:basics/detectorlifecycle"> </a><a class="target" name="toc2.10"> </a><h2>Detector Lifecycle</h2> |
| <p> |
| |
| |
| Detector registration is done by detector class, not by detector |
| instance. Lint will instantiate detectors on your behalf. It will |
| instantiate the detector once per analysis, so you can stash state on |
| the detector in fields and accumulate information for analysis at the |
| end. |
| |
| </p><p> |
| |
| There are some callbacks both before each individual file is analyzed |
| (<code>beforeCheckFile</code> and <code>afterCheckFile</code>), as well as before and after |
| analysis of all the modules (<code>beforeCheckRootProject</code> and |
| <code>afterCheckRootProject</code>). |
| |
| </p><p> |
| |
| This is for example how the ”unused resources“ check works: we store |
| all the resource declarations and resource references we find in the |
| project as we process each file, and then in the |
| <code>afterCheckRootProject</code> method we analyze the resource graph and |
| compute any resource declarations that are not reachable in the |
| reference graph, and then we report each of these as unused. |
| |
| </p> |
| <a class="target" name="scannerorder"> </a><a class="target" name="writingalintcheck:basics/scannerorder"> </a><a class="target" name="toc2.11"> </a><h2>Scanner Order</h2> |
| <p> |
| |
| |
| Some lint checks involve multiple scanners. This is pretty common in |
| Android, where we want to cross check consistency between data in |
| resource files with the code usages. For example, the <code>String.format</code> |
| check makes sure that the arguments passed to <code>String.format</code> match the |
| formatting strings specified in all the translation XML files. |
| |
| </p><p> |
| |
| Lint defines an exact order in which it processes scanners, and within |
| scanners, data. This makes it possible to write some detectors more |
| easily because you know that you'll encounter one type of data before |
| the other; you don't have to handle the opposite order. For example, in |
| our <code>String.format</code> example, we know that we'll always see the |
| formatting strings before we see the code with <code>String.format</code> calls, |
| so we can stash the formatting strings in a map, and when we process |
| the formatting calls in code, we can immediately issue reports; we |
| don't have to worry about encountering a formatting call for a |
| formatting string we haven't processed yet. |
| |
| </p><p> |
| |
| Here's lint's defined order: |
| |
| </p><p> |
| |
| </p><ol start="1"> |
| <li class="number">Android Manifest |
| </li> |
| <li class="number">Android resources XML files (alphabetical by folder type, so for |
| example layouts are processed before value files like translations) |
| </li> |
| <li class="number">Kotlin and Java files |
| </li> |
| <li class="number">Bytecode (local <code>.class</code> files and library <code>.jar</code> files) |
| </li> |
| <li class="number">Gradle files |
| </li> |
| <li class="number">Other files |
| </li> |
| <li class="number">ProGuard files |
| </li> |
| <li class="number">Property Files</li></ol> |
| |
| <p></p><p> |
| |
| Similarly, lint will always process libraries before the modules |
| that depend on them. |
| |
| </p><p> |
| |
| </p><div class="admonition tip">If you need to access something from later in the iteration order, |
| and it's not practical to store all the current data and instead |
| handle it when the later data is encountered, note that lint has |
| support for ”multi-pass analysis“: it can run multiple times over |
| the data. The way you invoke this is via |
| <code>context.driver.requestRepeat(this, …)</code>. This is actually how the |
| unused resource analysis works. Note however that this repeat is |
| only valid within the current module; you can't re-run the analysis |
| through the whole dependency graph.</div> |
| |
| <p></p> |
| <a class="target" name="implementingadetector:services"> </a><a class="target" name="writingalintcheck:basics/implementingadetector:services"> </a><a class="target" name="toc2.12"> </a><h2>Implementing a Detector: Services</h2> |
| <p> |
| |
| |
| In addition to the scanners, lint provides a number of services |
| to make implementation simpler. These include |
| |
| </p><p> |
| |
| </p><ul> |
| <li class="asterisk"><strong class="asterisk"><code>ConstantEvaluator</code></strong>: Performs evaluation of AST expressions, so |
| for example if we have the statements <code>x = 5; y = 2 * x</code>, the |
| constant evaluator can tell you that y is 10. This constant evaluator |
| can also be more permissive than a compiler's strict constant |
| evaluator; e.g. it can return concatenated strings where not all |
| parts are known, or it can use non-final initial values of fields. |
| This can help you find <em class="asterisk">possible</em> bugs instead of <em class="asterisk">certain</em> bugs. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk"><strong class="asterisk"><code>TypeEvaluator</code></strong>: Attempts to provide the concrete type of an |
| expression. For example, for the Java statements <code>Object s = new |
| StringBuilder(); Object o = s</code>, the type evaluator can tell you that |
| the type of <code>o</code> at this point is really <code>StringBuilder</code>. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk"><strong class="asterisk"><code>JavaEvaluator</code></strong>: Despite the unfortunate older name, this service |
| applies to both Kotlin and Java, and can for example provide |
| information about inheritance hierarchies, class lookup from fully |
| qualified names, etc. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk"><strong class="asterisk"><code>DataFlowAnalyzer</code></strong>: Data flow analysis within a method. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk">For Android analysis, there are several other important services, |
| like the <code>ResourceRepository</code> and the <code>ResourceEvaluator</code>. |
| |
| <p></p><p> |
| |
| </p></li> |
| <li class="asterisk">Finally, there are a number of utility methods; for example there is |
| an <code>editDistance</code> method used to find likely typos used by a number |
| of checks.</li></ul> |
| |
| <p></p> |
| <a class="target" name="scannerexample"> </a><a class="target" name="writingalintcheck:basics/scannerexample"> </a><a class="target" name="toc2.13"> </a><h2>Scanner Example</h2> |
| <p> |
| |
| |
| Let's create a <code>Detector</code> using one of the above scanners, |
| <code>XmlScanner</code>, which will look at all the XML files in the project and |
| if it encounters a <code><bitmap></code> tag it will report that <code><vector></code> should |
| be used instead: |
| |
| </p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line"><span class="hljs-keyword">import</span> com.android.tools.lint.detector.api.Detector</span> |
| <span class="line"><span class="hljs-keyword">import</span> com.android.tools.lint.detector.api.Detector.XmlScanner</span> |
| <span class="line"><span class="hljs-keyword">import</span> com.android.tools.lint.detector.api.Location</span> |
| <span class="line"><span class="hljs-keyword">import</span> com.android.tools.lint.detector.api.XmlContext</span> |
| <span class="line"><span class="hljs-keyword">import</span> org.w3c.dom.Element</span> |
| <span class="line"></span> |
| <span class="line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyDetector</span> : <span class="hljs-type">Detector</span></span>(), XmlScanner {</span> |
| <span class="line"> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getApplicableElements</span><span class="hljs-params">()</span></span> = listOf(<span class="hljs-string">"bitmap"</span>)</span> |
| <span class="line"></span> |
| <span class="line"> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">visitElement</span><span class="hljs-params">(context: <span class="hljs-type">XmlContext</span>, element: <span class="hljs-type">Element</span>)</span></span> {</span> |
| <span class="line"> <span class="hljs-keyword">val</span> incident = Incident(context, ISSUE)</span> |
| <span class="line"> .message( <span class="hljs-string">"Use `<vector>` instead of `<bitmap>`"</span>)</span> |
| <span class="line"> .at(element)</span> |
| <span class="line"> context.report(incident))</span> |
| <span class="line"> }</span> |
| <span class="line">}</span></div></code></pre><p> |
| |
| The above is using the new <code>Incident</code> API from Lint 7.0 and on; in |
| older versions you can use the following API, which still works in 7.0: |
| |
| </p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyDetector</span> : <span class="hljs-type">Detector</span></span>(), XmlScanner {</span> |
| <span class="line"> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getApplicableElements</span><span class="hljs-params">()</span></span> = listOf(<span class="hljs-string">"bitmap"</span>)</span> |
| <span class="line"></span> |
| <span class="line"> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">visitElement</span><span class="hljs-params">(context: <span class="hljs-type">XmlContext</span>, element: <span class="hljs-type">Element</span>)</span></span> {</span> |
| <span class="line"> context.report(ISSUE, context.getLocation(element),</span> |
| <span class="line"> <span class="hljs-string">"Use `<vector>` instead of `<bitmap>`"</span>)</span> |
| <span class="line"> }</span> |
| <span class="line">}</span></div></code></pre><p> |
| |
| The second, older form, may seem simpler, but the new API allows a lot |
| more metadata to be attached to the report, such as an override |
| severity. You don't have to convert to the builder syntax to do this; |
| you could also have written the second form as |
| |
| </p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line">context.report(Incident(ISSUE, context.getLocation(element),</span> |
| <span class="line"> <span class="hljs-string">"Use `<vector>` instead of `<bitmap>`"</span>))</span></div></code></pre> |
| <a class="target" name="analyzingkotlinandjavacode"> </a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode"> </a><a class="target" name="toc2.14"> </a><h2>Analyzing Kotlin and Java Code</h2> |
| |
| <a class="target" name="uast"> </a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/uast"> </a><a class="target" name="toc2.14.1"> </a><h3>UAST</h3> |
| <p> |
| |
| |
| To analyze Kotlin and Java code, lint offers an abstract syntax tree, |
| or ”AST“, for the code. |
| |
| </p><p> |
| |
| This AST is called ”UAST“, for ”Universal Abstract Syntax Tree“, which |
| represents multiple languages in the same way, hiding the language |
| specific details like whether there is a semicolon at the end of the |
| statements or whether the way an annotation class is declared is as |
| <code>@interface</code> or <code>annotation class</code>, and so on. |
| |
| </p><p> |
| |
| This makes it possible to write a single analyzer which works |
| (”universally“) across all languages supported by UAST. And this is |
| very useful; most lint checks are doing something API or data-flow |
| specific, not something language specific. If however you do need to |
| implement something very language specific, see the next section, |
| “PSI”. |
| |
| </p><p> |
| |
| In UAST, each element is called a <strong class="asterisk"><code>UElement</code></strong>, and there are a |
| number of subclasses — <code>UFile</code> for the compilation unit, <code>UClass</code> for |
| a class, <code>UMethod</code> for a method, <code>UExpression</code> for an expression, |
| <code>UIfExpression</code> for an <code>if</code>-expression, and so on. |
| |
| </p><p> |
| |
| Here's a visualization of an AST in UAST for two equivalent programs |
| written in Kotlin and Java. These programs both result in the same |
| AST, shown on the right: a <code>UFile</code> compilation unit, containing |
| a <code>UClass</code> named <code>MyTest</code>, containing <code>UField</code> named s which has |
| an initializer setting the initial value to <code>hello</code>. |
| |
| </p><p> |
| |
| <svg class="diagram" xmlns="http://www.w3.org/2000/svg" version="1.1" height="352" width="568" style="margin:0 auto 0 auto;"><g transform="translate(8,16 )"> |
| <path d="M 8,32 L 8,112 " style="fill:none;"></path> |
| <path d="M 8,160 L 8,256 " style="fill:none;"></path> |
| <path d="M 208,160 L 208,256 " style="fill:none;"></path> |
| <path d="M 232,32 L 232,112 " style="fill:none;"></path> |
| <path d="M 344,64 L 344,96 " style="fill:none;"></path> |
| <path d="M 344,128 L 344,160 " style="fill:none;"></path> |
| <path d="M 8,32 L 232,32 " style="fill:none;"></path> |
| <path d="M 320,32 L 368,32 " style="fill:none;"></path> |
| <path d="M 320,64 L 368,64 " style="fill:none;"></path> |
| <path d="M 296,96 L 392,96 " style="fill:none;"></path> |
| <path d="M 8,112 L 232,112 " style="fill:none;"></path> |
| <path d="M 296,128 L 392,128 " style="fill:none;"></path> |
| <path d="M 8,160 L 208,160 " style="fill:none;"></path> |
| <path d="M 320,160 L 376,160 " style="fill:none;"></path> |
| <path d="M 320,192 L 376,192 " style="fill:none;"></path> |
| <path d="M 8,256 L 208,256 " style="fill:none;"></path> |
| <path d="M 192,272 L 280,272 " style="fill:none;"></path> |
| <path d="M 352,272 L 536,272 " style="fill:none;"></path> |
| <path d="M 192,304 L 280,304 " style="fill:none;"></path> |
| <path d="M 352,304 L 536,304 " style="fill:none;"></path> |
| <path d="M 376,192 L 416,272 " style="fill:none;"></path> |
| <path d="M 280,272 L 320,192 " style="fill:none;"></path> |
| <path d="M 320,32 C 303.2,32 304,48 304,48 " style="fill:none;"></path> |
| <path d="M 368,32 C 384.8,32 384,48 384,48 " style="fill:none;"></path> |
| <path d="M 320,64 C 303.2,64 304,48 304,48 " style="fill:none;"></path> |
| <path d="M 368,64 C 384.8,64 384,48 384,48 " style="fill:none;"></path> |
| <path d="M 296,96 C 279.2,96 280,112 280,112 " style="fill:none;"></path> |
| <path d="M 392,96 C 408.8,96 408,112 408,112 " style="fill:none;"></path> |
| <path d="M 296,128 C 279.2,128 280,112 280,112 " style="fill:none;"></path> |
| <path d="M 392,128 C 408.8,128 408,112 408,112 " style="fill:none;"></path> |
| <path d="M 320,160 C 303.2,160 304,176 304,176 " style="fill:none;"></path> |
| <path d="M 376,160 C 392.8,160 392,176 392,176 " style="fill:none;"></path> |
| <path d="M 320,192 C 303.2,192 304,176 304,176 " style="fill:none;"></path> |
| <path d="M 376,192 C 392.8,192 392,176 392,176 " style="fill:none;"></path> |
| <path d="M 192,272 C 175.2,272 176,288 176,288 " style="fill:none;"></path> |
| <path d="M 280,272 C 296.8,272 296,288 296,288 " style="fill:none;"></path> |
| <path d="M 352,272 C 335.2,272 336,288 336,288 " style="fill:none;"></path> |
| <path d="M 536,272 C 552.8,272 552,288 552,288 " style="fill:none;"></path> |
| <path d="M 192,304 C 175.2,304 176,288 176,288 " style="fill:none;"></path> |
| <path d="M 280,304 C 296.8,304 296,288 296,288 " style="fill:none;"></path> |
| <path d="M 352,304 C 335.2,304 336,288 336,288 " style="fill:none;"></path> |
| <path d="M 536,304 C 552.8,304 552,288 552,288 " style="fill:none;"></path> |
| <g transform="translate(0,0)"><text text-anchor="middle" x="8" y="20">M</text><text text-anchor="middle" x="16" y="20">y</text><text text-anchor="middle" x="24" y="20">T</text><text text-anchor="middle" x="32" y="20">e</text><text text-anchor="middle" x="40" y="20">s</text><text text-anchor="middle" x="48" y="20">t</text><text text-anchor="middle" x="56" y="20">.</text><text text-anchor="middle" x="64" y="20">k</text><text text-anchor="middle" x="72" y="20">t</text><text text-anchor="middle" x="80" y="20">:</text><text text-anchor="middle" x="328" y="20">U</text><text text-anchor="middle" x="336" y="20">A</text><text text-anchor="middle" x="344" y="20">S</text><text text-anchor="middle" x="352" y="20">T</text><text text-anchor="middle" x="360" y="20">:</text><text text-anchor="middle" x="24" y="52">p</text><text text-anchor="middle" x="32" y="52">a</text><text text-anchor="middle" x="40" y="52">c</text><text text-anchor="middle" x="48" y="52">k</text><text text-anchor="middle" x="56" y="52">a</text><text text-anchor="middle" x="64" y="52">g</text><text text-anchor="middle" x="72" y="52">e</text><text text-anchor="middle" x="88" y="52">t</text><text text-anchor="middle" x="96" y="52">e</text><text text-anchor="middle" x="104" y="52">s</text><text text-anchor="middle" x="112" y="52">t</text><text text-anchor="middle" x="120" y="52">.</text><text text-anchor="middle" x="128" y="52">p</text><text text-anchor="middle" x="136" y="52">k</text><text text-anchor="middle" x="144" y="52">g</text><text text-anchor="middle" x="328" y="52">U</text><text text-anchor="middle" x="336" y="52">F</text><text text-anchor="middle" x="344" y="52">i</text><text text-anchor="middle" x="352" y="52">l</text><text text-anchor="middle" x="360" y="52">e</text><text text-anchor="middle" x="24" y="68">c</text><text text-anchor="middle" x="32" y="68">l</text><text text-anchor="middle" x="40" y="68">a</text><text text-anchor="middle" x="48" y="68">s</text><text text-anchor="middle" x="56" y="68">s</text><text text-anchor="middle" x="72" y="68">M</text><text text-anchor="middle" x="80" y="68">y</text><text text-anchor="middle" x="88" y="68">T</text><text text-anchor="middle" x="96" y="68">e</text><text text-anchor="middle" x="104" y="68">s</text><text text-anchor="middle" x="112" y="68">t</text><text text-anchor="middle" x="128" y="68">{</text><text text-anchor="middle" x="40" y="84">p</text><text text-anchor="middle" x="48" y="84">r</text><text text-anchor="middle" x="56" y="84">i</text><text text-anchor="middle" x="64" y="84">v</text><text text-anchor="middle" x="72" y="84">a</text><text text-anchor="middle" x="80" y="84">t</text><text text-anchor="middle" x="88" y="84">e</text><text text-anchor="middle" x="104" y="84">v</text><text text-anchor="middle" x="112" y="84">a</text><text text-anchor="middle" x="120" y="84">l</text><text text-anchor="middle" x="136" y="84">s</text><text text-anchor="middle" x="152" y="84">=</text><text text-anchor="middle" x="168" y="84">“</text><text text-anchor="middle" x="176" y="84">h</text><text text-anchor="middle" x="184" y="84">e</text><text text-anchor="middle" x="192" y="84">l</text><text text-anchor="middle" x="200" y="84">l</text><text text-anchor="middle" x="208" y="84">o</text><text text-anchor="middle" x="216" y="84">”</text><text text-anchor="middle" x="24" y="100">}</text><text text-anchor="middle" x="296" y="116">U</text><text text-anchor="middle" x="304" y="116">C</text><text text-anchor="middle" x="312" y="116">l</text><text text-anchor="middle" x="320" y="116">a</text><text text-anchor="middle" x="328" y="116">s</text><text text-anchor="middle" x="336" y="116">s</text><text text-anchor="middle" x="352" y="116">M</text><text text-anchor="middle" x="360" y="116">y</text><text text-anchor="middle" x="368" y="116">T</text><text text-anchor="middle" x="376" y="116">e</text><text text-anchor="middle" x="384" y="116">s</text><text text-anchor="middle" x="392" y="116">t</text><text text-anchor="middle" x="8" y="148">M</text><text text-anchor="middle" x="16" y="148">y</text><text text-anchor="middle" x="24" y="148">T</text><text text-anchor="middle" x="32" y="148">e</text><text text-anchor="middle" x="40" y="148">s</text><text text-anchor="middle" x="48" y="148">t</text><text text-anchor="middle" x="56" y="148">.</text><text text-anchor="middle" x="64" y="148">j</text><text text-anchor="middle" x="72" y="148">a</text><text text-anchor="middle" x="80" y="148">v</text><text text-anchor="middle" x="88" y="148">a</text><text text-anchor="middle" x="96" y="148">:</text><text text-anchor="middle" x="24" y="180">p</text><text text-anchor="middle" x="32" y="180">a</text><text text-anchor="middle" x="40" y="180">c</text><text text-anchor="middle" x="48" y="180">k</text><text text-anchor="middle" x="56" y="180">a</text><text text-anchor="middle" x="64" y="180">g</text><text text-anchor="middle" x="72" y="180">e</text><text text-anchor="middle" x="88" y="180">t</text><text text-anchor="middle" x="96" y="180">e</text><text text-anchor="middle" x="104" y="180">s</text><text text-anchor="middle" x="112" y="180">t</text><text text-anchor="middle" x="120" y="180">.</text><text text-anchor="middle" x="128" y="180">p</text><text text-anchor="middle" x="136" y="180">k</text><text text-anchor="middle" x="144" y="180">g</text><text text-anchor="middle" x="152" y="180">;</text><text text-anchor="middle" x="320" y="180">U</text><text text-anchor="middle" x="328" y="180">F</text><text text-anchor="middle" x="336" y="180">i</text><text text-anchor="middle" x="344" y="180">e</text><text text-anchor="middle" x="352" y="180">l</text><text text-anchor="middle" x="360" y="180">d</text><text text-anchor="middle" x="376" y="180">s</text><text text-anchor="middle" x="24" y="196">p</text><text text-anchor="middle" x="32" y="196">u</text><text text-anchor="middle" x="40" y="196">b</text><text text-anchor="middle" x="48" y="196">l</text><text text-anchor="middle" x="56" y="196">i</text><text text-anchor="middle" x="64" y="196">c</text><text text-anchor="middle" x="80" y="196">c</text><text text-anchor="middle" x="88" y="196">l</text><text text-anchor="middle" x="96" y="196">a</text><text text-anchor="middle" x="104" y="196">s</text><text text-anchor="middle" x="112" y="196">s</text><text text-anchor="middle" x="128" y="196">M</text><text text-anchor="middle" x="136" y="196">y</text><text text-anchor="middle" x="144" y="196">T</text><text text-anchor="middle" x="152" y="196">e</text><text text-anchor="middle" x="160" y="196">s</text><text text-anchor="middle" x="168" y="196">t</text><text text-anchor="middle" x="184" y="196">{</text><text text-anchor="middle" x="48" y="212">p</text><text text-anchor="middle" x="56" y="212">r</text><text text-anchor="middle" x="64" y="212">i</text><text text-anchor="middle" x="72" y="212">v</text><text text-anchor="middle" x="80" y="212">a</text><text text-anchor="middle" x="88" y="212">t</text><text text-anchor="middle" x="96" y="212">e</text><text text-anchor="middle" x="112" y="212">S</text><text text-anchor="middle" x="120" y="212">t</text><text text-anchor="middle" x="128" y="212">r</text><text text-anchor="middle" x="136" y="212">i</text><text text-anchor="middle" x="144" y="212">n</text><text text-anchor="middle" x="152" y="212">g</text><text text-anchor="middle" x="168" y="212">s</text><text text-anchor="middle" x="184" y="212">=</text><text text-anchor="middle" x="80" y="228">“</text><text text-anchor="middle" x="88" y="228">h</text><text text-anchor="middle" x="96" y="228">e</text><text text-anchor="middle" x="104" y="228">l</text><text text-anchor="middle" x="112" y="228">l</text><text text-anchor="middle" x="120" y="228">o</text><text text-anchor="middle" x="128" y="228">”</text><text text-anchor="middle" x="136" y="228">;</text><text text-anchor="middle" x="24" y="244">}</text><text text-anchor="middle" x="184" y="292">U</text><text text-anchor="middle" x="192" y="292">I</text><text text-anchor="middle" x="200" y="292">d</text><text text-anchor="middle" x="208" y="292">e</text><text text-anchor="middle" x="216" y="292">n</text><text text-anchor="middle" x="224" y="292">t</text><text text-anchor="middle" x="232" y="292">i</text><text text-anchor="middle" x="240" y="292">f</text><text text-anchor="middle" x="248" y="292">i</text><text text-anchor="middle" x="256" y="292">e</text><text text-anchor="middle" x="264" y="292">r</text><text text-anchor="middle" x="280" y="292">s</text><text text-anchor="middle" x="352" y="292">U</text><text text-anchor="middle" x="360" y="292">L</text><text text-anchor="middle" x="368" y="292">i</text><text text-anchor="middle" x="376" y="292">t</text><text text-anchor="middle" x="384" y="292">e</text><text text-anchor="middle" x="392" y="292">r</text><text text-anchor="middle" x="400" y="292">a</text><text text-anchor="middle" x="408" y="292">l</text><text text-anchor="middle" x="416" y="292">E</text><text text-anchor="middle" x="424" y="292">x</text><text text-anchor="middle" x="432" y="292">p</text><text text-anchor="middle" x="440" y="292">r</text><text text-anchor="middle" x="448" y="292">e</text><text text-anchor="middle" x="456" y="292">s</text><text text-anchor="middle" x="464" y="292">s</text><text text-anchor="middle" x="472" y="292">i</text><text text-anchor="middle" x="480" y="292">o</text><text text-anchor="middle" x="488" y="292">n</text><text text-anchor="middle" x="504" y="292">h</text><text text-anchor="middle" x="512" y="292">e</text><text text-anchor="middle" x="520" y="292">l</text><text text-anchor="middle" x="528" y="292">l</text><text text-anchor="middle" x="536" y="292">o</text></g></g></svg> |
| |
| </p><p> |
| |
| </p><div class="admonition tip">The name “UAST” is a bit misleading; it is not some sort of superset |
| of all possible syntax trees; instead, think of this as the “Java |
| view” of all code. So, for example, there isn’t a <code>UProperty</code> node |
| which represents Kotlin properties. Instead, the AST will look the |
| same as if the property had been implemented in Java: it will |
| contain a private field and a public getter and a public setter |
| (unless of course the Kotlin property specifies a private setter). |
| If you’ve written code in Kotlin and have tried to access that |
| Kotlin code from a Java file you will see the same thing — the |
| “Java view” of Kotlin. The next section, “PSI“, will discuss how to |
| do more language specific analysis.</div> |
| |
| <p></p> |
| <a class="target" name="uastexample"> </a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/uastexample"> </a><a class="target" name="toc2.14.2"> </a><h3>UAST Example</h3> |
| <p> |
| |
| |
| Here's an example (from the built-in <code>AlarmDetector</code> for Android) which |
| shows all of the above in practice; this is a lint check which makes |
| sure that if anyone calls <code>AlarmManager.setRepeating</code>, the second |
| argument is at least 5,000 and the third argument is at least 60,000. |
| |
| </p><p> |
| |
| Line 1 says we want to have line 3 called whenever lint comes across a |
| method to <code>setRepeating</code>. |
| |
| </p><p> |
| |
| On lines 8-4 we make sure we're talking about the correct method on the |
| correct class with the correct signature. This uses the <code>JavaEvaluator</code> |
| to check that the called method is a member of the named class. This is |
| necessary because the callback would also be invoked if lint came |
| across a method call like <code>Unrelated.setRepeating</code>; the |
| <code>visitMethodCall</code> callback only matches by name, not receiver. |
| |
| </p><p> |
| |
| On line 36 we use the <code>ConstantEvaluator</code> to compute the value of each |
| argument passed in. This will let this lint check not only handle cases |
| where you're specifying a specific value directly in the argument list, |
| but also for example referencing a constant from elsewhere. |
| |
| </p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getApplicableMethodNames</span><span class="hljs-params">()</span></span>: List<string> = listOf(<span class="hljs-string">"setRepeating"</span>)</span> |
| <span class="line"></span> |
| <span class="line"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">visitMethodCall</span><span class="hljs-params">(</span> |
| <span class="line"> context: <span class="hljs-type">JavaContext</span>,</span> |
| <span class="line"> node: <span class="hljs-type">UCallExpression</span>,</span> |
| <span class="line"> method: <span class="hljs-type">PsiMethod</span></span> |
| <span class="line">)</span></span> {</span> |
| <span class="line"> <span class="hljs-keyword">val</span> evaluator = context.evaluator</span> |
| <span class="line"> <span class="hljs-keyword">if</span> (evaluator.isMemberInClass(method, <span class="hljs-string">"android.app.AlarmManager"</span>) &&</span> |
| <span class="line"> evaluator.getParameterCount(method) == <span class="hljs-number">4</span></span> |
| <span class="line"> ) {</span> |
| <span class="line"> ensureAtLeast(context, node, <span class="hljs-number">1</span>, <span class="hljs-number">5000L</span>)</span> |
| <span class="line"> ensureAtLeast(context, node, <span class="hljs-number">2</span>, <span class="hljs-number">60000L</span>)</span> |
| <span class="line"> }</span> |
| <span class="line">}</span> |
| <span class="line"></span> |
| <span class="line"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ensureAtLeast</span><span class="hljs-params">(</span> |
| <span class="line"> context: <span class="hljs-type">JavaContext</span>,</span> |
| <span class="line"> node: <span class="hljs-type">UCallExpression</span>,</span> |
| <span class="line"> parameter: <span class="hljs-type">Int</span>,</span> |
| <span class="line"> min: <span class="hljs-type">Long</span></span> |
| <span class="line">)</span></span> {</span> |
| <span class="line"> <span class="hljs-keyword">val</span> argument = node.valueArguments[parameter]</span> |
| <span class="line"> <span class="hljs-keyword">val</span> value = getLongValue(context, argument)</span> |
| <span class="line"> <span class="hljs-keyword">if</span> (value < min) {</span> |
| <span class="line"> <span class="hljs-keyword">val</span> message = <span class="hljs-string">"Value will be forced up to <span class="hljs-variable">$min</span> as of Android 5.1; "</span> +</span> |
| <span class="line"> <span class="hljs-string">"don't rely on this to be exact"</span></span> |
| <span class="line"> context.report(ISSUE, argument, context.getLocation(argument), message)</span> |
| <span class="line"> }</span> |
| <span class="line">}</span> |
| <span class="line"></span> |
| <span class="line"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getLongValue</span><span class="hljs-params">(</span> |
| <span class="line"> context: <span class="hljs-type">JavaContext</span>,</span> |
| <span class="line"> argument: <span class="hljs-type">UExpression</span></span> |
| <span class="line">)</span></span>: <span class="hljs-built_in">Long</span> {</span> |
| <span class="line"> <span class="hljs-keyword">val</span> value = ConstantEvaluator.evaluate(context, argument)</span> |
| <span class="line"> <span class="hljs-keyword">if</span> (value <span class="hljs-keyword">is</span> Number) {</span> |
| <span class="line"> <span class="hljs-keyword">return</span> value.toLong()</span> |
| <span class="line"> }</span> |
| <span class="line"></span> |
| <span class="line"> <span class="hljs-keyword">return</span> java.lang.<span class="hljs-built_in">Long</span>.MAX_VALUE</span> |
| <span class="line">}</span></div></code></pre> |
| <a class="target" name="lookingupuast"> </a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/lookingupuast"> </a><a class="target" name="toc2.14.3"> </a><h3>Looking up UAST</h3> |
| <p> |
| |
| |
| To write your detector's analysis, you need to know what the AST for |
| your code of interest looks like. Instead of trying to figure it out by |
| examining the elements under a debugger, a simple way to find out is to |
| ”pretty print“ it, using the <code>UElement</code> extension method |
| <strong class="asterisk"><code>asRecursiveLogString</code></strong>. |
| |
| </p><p> |
| |
| For example, given the following unit test: |
| |
| </p><pre class="listing tilde"><code><span class="line">lint().files(</span> |
| <span class="line"> kotlin(<span class="hljs-string">""</span></span> |
| <span class="line"> <span class="hljs-operator">+</span> <span class="hljs-string">"package test.pkg<span class="hljs-subst">\n</span>"</span></span> |
| <span class="line"> <span class="hljs-operator">+</span> <span class="hljs-string">"<span class="hljs-subst">\n</span>"</span></span> |
| <span class="line"> <span class="hljs-operator">+</span> <span class="hljs-string">"class MyTest {<span class="hljs-subst">\n</span>"</span></span> |
| <span class="line"> <span class="hljs-operator">+</span> <span class="hljs-string">" val s: String = <span class="hljs-subst">\"</span>hello<span class="hljs-subst">\"</span><span class="hljs-subst">\n</span>"</span></span> |
| <span class="line"> <span class="hljs-operator">+</span> <span class="hljs-string">"}<span class="hljs-subst">\n</span>"</span>), <span class="hljs-operator">...</span></span></code></pre><p> |
| |
| If you evaluate <code>context.uastFile?.asRecursiveLogString()</code> from |
| one of the callbacks, it will print this: |
| |
| </p><pre class="listing backtick"><code><span class="line">UFile (package = test.pkg)</span> |
| <span class="line"> UClass (name = MyTest)</span> |
| <span class="line"> UField (name = s)</span> |
| <span class="line"> UAnnotation (fqName = org.jetbrains.annotations.NotNull)</span> |
| <span class="line"> ULiteralExpression (value = "hello")</span> |
| <span class="line"> UAnnotationMethod (name = getS)</span> |
| <span class="line"> UAnnotationMethod (name = MyTest)</span></code></pre><p> |
| |
| (This also illustrates the earlier point about UAST representing the |
| Java view of the code; here the read-only public Kotlin property ”s“ is |
| represented by both a private field <code>s</code> and a public getter method, |
| <code>getS()</code>.) |
| |
| </p> |
| <a class="target" name="resolving"> </a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/resolving"> </a><a class="target" name="toc2.14.4"> </a><h3>Resolving</h3> |
| <p> |
| |
| |
| When you have a method call, or a field reference, you may want to take |
| a look at the called method or field. This is called ”resolving“, and |
| UAST supports it directly; on a <code>UCallExpression</code> for example, call |
| <code>.resolve()</code>, which returns a <code>PsiMethod</code>, which is like a <code>UMethod</code>, |
| but may not represent a method we have source for (which for example |
| would be the case if you resolve a reference to the JDK or to a library |
| we do not have sources for). You can call <code>.toUElement()</code> on the |
| PSI element to try to convert it to UAST if source is available. |
| |
| </p><p> |
| |
| </p><div class="admonition warning">Resolving only works if lint has a correct classpath such that the |
| referenced method, field or class are actually present. If it is |
| not, resolve will return null, and various lint callbacks will not |
| be invoked. This is a common source of questions for lint checks |
| ”not working“; it frequently comes up in lint unit tests where a |
| test file will reference some API that isn't actually included in |
| the class path. The recommended approach for this is to declare |
| local stubs. See the <a href="#lintcheckunittesting">unit testing</a> chapter |
| for more details about this.</div> |
| |
| <p></p> |
| <a class="target" name="psi"> </a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/psi"> </a><a class="target" name="toc2.14.5"> </a><h3>PSI</h3> |
| <p> |
| |
| |
| PSI is short for ”Program Structure Interface“, and is IntelliJ's AST |
| abstraction used for all language modeling in the IDE. |
| |
| </p><p> |
| |
| Note that there is a <strong class="asterisk">different</strong> PSI representation for each |
| language. Java and Kotlin have completely different PSI classes |
| involved. This means that writing a lint check using PSI would involve |
| writing a lot of logic twice; once for Java, and once for Kotlin. (And |
| the Kotlin PSI is a bit trickier to work with.) |
| |
| </p><p> |
| |
| That's what UAST is for: there's a ”bridge“ from the Java PSI to UAST |
| and there's a bridge from the Kotlin PSI to UAST, and your lint check |
| just analyzes UAST. |
| |
| </p><p> |
| |
| However, there are a few scenarios where we have to use PSI. |
| |
| </p><p> |
| |
| The first, and most common one, is listed in the previous section on |
| resolving. UAST does not completely replace PSI; in fact, PSI leaks |
| through in part of the UAST API surface. For example, |
| <code>UMethod.resolve()</code> returns a <code>PsiMethod</code>. And more importantly, |
| <code>UMethod</code> <strong class="asterisk">extends</strong> <code>PsiMethod</code>. |
| |
| </p><p> |
| |
| </p><div class="admonition warning">For historical reasons, <code>PsiMethod</code> and other PSI classes contain |
| some unfortunate APIs that only work for Java, such as asking for |
| the method body. Because <code>UMethod</code> extends <code>PsiMethod</code>, you might be |
| tempted to call <code>getBody()</code> on it, but this will return null from |
| Kotlin. If your unit tests for your lint check only have test cases |
| written in Java, you may not realize that your check is doing the |
| wrong thing and won't work on Kotlin code. It should call <code>uastBody</code> |
| on the <code>UMethod</code> instead. Lint's special detector for lint detectors |
| looks for this and a few other scenarios (such as calling <code>parent</code> |
| instead of <code>uastParent</code>), so be sure to configure it for your |
| project.</div> |
| |
| <p></p><p> |
| |
| When you are dealing with ”signatures“ — looking at classes and |
| class inheritance, methods, parameters and so on — using PSI is |
| fine — and unavoidable since UAST does not represent bytecode |
| (though in the future it potentially could, via a decompiler) |
| or any other JVM languages than Kotlin and Java. |
| |
| </p><p> |
| |
| However, if you are looking at anything <em class="asterisk">inside</em> a method or class |
| or field initializer, you <strong class="asterisk">must</strong> use UAST. |
| |
| </p><p> |
| |
| The <strong class="asterisk">second</strong> scenario where you may need to use PSI is where you have |
| to do something language specific which is not represented in UAST. For |
| example, if you are trying to look up the names or default values of a |
| parameter, or whether a given class is a companion object, then you'll |
| need to dip into Kotlin PSI. |
| |
| </p><p> |
| |
| There is usually no need to look at Java PSI since UAST fully covers |
| it, unless you want to look at individual details like specific |
| whitespace between AST nodes, which is represented in PSI but not UAST. |
| |
| </p><p> |
| |
| </p><div class="admonition tip">You can find additional documentation from JetBrains for both |
| <a href="https://plugins.jetbrains.com/docs/intellij/psi.html">PSI</a> and |
| <a href="https://plugins.jetbrains.com/docs/intellij/uast.html">UAST</a>. |
| Just note that their documentation is aimed at IDE plugin developers |
| rather than lint developers.</div> |
| |
| <p></p> |
| <a class="target" name="testing"> </a><a class="target" name="writingalintcheck:basics/testing"> </a><a class="target" name="toc2.15"> </a><h2>Testing</h2> |
| <p> |
| |
| |
| Writing unit tests for the lint check is important, and this is covered |
| in detail in the dedicated <a href="#lintcheckunittesting">unit testing</a> |
| chapter. |
| |
| </p><p> |
| |
| |
| |
| </p> |
| <a class="target" name="example:samplelintcheckgithubproject"> </a><a class="target" name="example:samplelintcheckgithubproject"> </a><a class="target" name="toc3"> </a><h1>Example: Sample Lint Check GitHub Project</h1> |
| <p> |
| |
| |
| The <a href="https://github.com/googlesamples/android-custom-lint-rules"></a><a href="https://github.com/googlesamples/android-custom-lint-rules" class="url">https://github.com/googlesamples/android-custom-lint-rules</a> |
| GitHub project provides a sample lint check which shows a working |
| skeleton. |
| |
| </p><p> |
| |
| This chapter walks through that sample project and explains |
| what and why. |
| |
| </p> |
| <a class="target" name="projectlayout"> </a><a class="target" name="example:samplelintcheckgithubproject/projectlayout"> </a><a class="target" name="toc3.1"> </a><h2>Project Layout</h2> |
| <p> |
| |
| |
| Here's the project layout of the sample project: |
| |
| </p><p> |
| |
| <svg class="diagram" xmlns="http://www.w3.org/2000/svg" version="1.1" height="96" width="528" style="margin:0 auto 0 auto;"><g transform="translate(8,16 )"> |
| <path d="M 32,16 L 32,48 " style="fill:none;"></path> |
| <path d="M 72,16 L 72,48 " style="fill:none;"></path> |
| <path d="M 224,16 L 224,48 " style="fill:none;"></path> |
| <path d="M 296,16 L 296,48 " style="fill:none;"></path> |
| <path d="M 424,16 L 424,48 " style="fill:none;"></path> |
| <path d="M 488,16 L 488,48 " style="fill:none;"></path> |
| <path d="M 32,16 L 72,16 " style="fill:none;"></path> |
| <path d="M 224,16 L 296,16 " style="fill:none;"></path> |
| <path d="M 424,16 L 488,16 " style="fill:none;"></path> |
| <path d="M 72,32 L 216,32 " style="fill:none;"></path> |
| <path d="M 296,32 L 416,32 " style="fill:none;"></path> |
| <path d="M 32,48 L 72,48 " style="fill:none;"></path> |
| <path d="M 224,48 L 296,48 " style="fill:none;"></path> |
| <path d="M 424,48 L 488,48 " style="fill:none;"></path> |
| <polygon points="424,32 412,26.4 412,37.6 " style="stroke:none" transform="rotate(0,416,32 )"></polygon> |
| <polygon points="224,32 212,26.4 212,37.6 " style="stroke:none" transform="rotate(0,216,32 )"></polygon> |
| <g transform="translate(0,0)"><text text-anchor="middle" x="96" y="20">i</text><text text-anchor="middle" x="104" y="20">m</text><text text-anchor="middle" x="112" y="20">p</text><text text-anchor="middle" x="120" y="20">l</text><text text-anchor="middle" x="128" y="20">e</text><text text-anchor="middle" x="136" y="20">m</text><text text-anchor="middle" x="144" y="20">e</text><text text-anchor="middle" x="152" y="20">n</text><text text-anchor="middle" x="160" y="20">t</text><text text-anchor="middle" x="168" y="20">a</text><text text-anchor="middle" x="176" y="20">t</text><text text-anchor="middle" x="184" y="20">i</text><text text-anchor="middle" x="192" y="20">o</text><text text-anchor="middle" x="200" y="20">n</text><text text-anchor="middle" x="320" y="20">l</text><text text-anchor="middle" x="328" y="20">i</text><text text-anchor="middle" x="336" y="20">n</text><text text-anchor="middle" x="344" y="20">t</text><text text-anchor="middle" x="352" y="20">P</text><text text-anchor="middle" x="360" y="20">u</text><text text-anchor="middle" x="368" y="20">b</text><text text-anchor="middle" x="376" y="20">l</text><text text-anchor="middle" x="384" y="20">i</text><text text-anchor="middle" x="392" y="20">s</text><text text-anchor="middle" x="400" y="20">h</text><text text-anchor="middle" x="40" y="36">:</text><text text-anchor="middle" x="48" y="36">a</text><text text-anchor="middle" x="56" y="36">p</text><text text-anchor="middle" x="64" y="36">p</text><text text-anchor="middle" x="232" y="36">:</text><text text-anchor="middle" x="240" y="36">l</text><text text-anchor="middle" x="248" y="36">i</text><text text-anchor="middle" x="256" y="36">b</text><text text-anchor="middle" x="264" y="36">r</text><text text-anchor="middle" x="272" y="36">a</text><text text-anchor="middle" x="280" y="36">r</text><text text-anchor="middle" x="288" y="36">y</text><text text-anchor="middle" x="432" y="36">:</text><text text-anchor="middle" x="440" y="36">c</text><text text-anchor="middle" x="448" y="36">h</text><text text-anchor="middle" x="456" y="36">e</text><text text-anchor="middle" x="464" y="36">c</text><text text-anchor="middle" x="472" y="36">k</text><text text-anchor="middle" x="480" y="36">s</text></g></g></svg> |
| |
| </p><p> |
| |
| We have an application module, <code>app</code>, which depends (via an |
| <code>implementation</code> dependency) on a <code>library</code>, and the library itself has |
| a <code>lintPublish</code> dependency on the <code>checks</code> project. |
| |
| </p> |
| <a class="target" name=":checks"> </a><a class="target" name="example:samplelintcheckgithubproject/:checks"> </a><a class="target" name="toc3.2"> </a><h2>:checks</h2> |
| <p> |
| |
| |
| The <code>checks</code> project is where the actual lint checks are implemented. |
| This project is a plain Kotlin or plain Java Gradle project: |
| |
| </p><pre class="listing tilde"><code><span class="line">apply plugin: <span class="hljs-string">'java-library'</span></span> |
| <span class="line">apply plugin: <span class="hljs-string">'kotlin'</span></span></code></pre><p> |
| |
| |
| |
| </p><div class="admonition tip">If you look at the sample project, you'll see a third plugin |
| applied: <code>apply plugin: 'com.android.lint'</code>. This pulls in the |
| standalone Lint Gradle plugin, which adds a lint target to this |
| Kotlin project. This means that you can run <code>./gradlew lint</code> on the |
| <code>:checks</code> project too. This is useful because lint ships with a |
| dozen lint checks that look for mistakes in lint detectors! This |
| includes warnings about using the wrong UAST methods, invalid id |
| formats, words in messages which look like code which should |
| probably be surrounded by apostrophes, etc.</div> |
| |
| <p></p><p> |
| |
| The Gradle file also declares the dependencies on lint APIs |
| that our detector needs: |
| |
| </p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line">dependencies {</span> |
| <span class="line"> compileOnly <span class="hljs-string">"com.android.tools.lint:lint-api:<span class="hljs-variable">$lintVersion</span>"</span></span> |
| <span class="line"> compileOnly <span class="hljs-string">"com.android.tools.lint:lint-checks:<span class="hljs-variable">$lintVersion</span>"</span></span> |
| <span class="line"> testImplementation <span class="hljs-string">"com.android.tools.lint:lint-tests:<span class="hljs-variable">$lintVersion</span>"</span></span> |
| <span class="line">}</span></div></code></pre><p> |
| |
| The second dependency is usually not necessary; you just need to depend |
| on the Lint API. However, the built-in checks define a lot of |
| additional infrastructure which it's sometimes convenient to depend on, |
| such as <code>ApiLookup</code> which lets you look up the required API level for a |
| given method, and so on. Don't add the dependency until you need it. |
| |
| </p> |
| <a class="target" name="lintversion?"> </a><a class="target" name="example:samplelintcheckgithubproject/lintversion?"> </a><a class="target" name="toc3.3"> </a><h2>lintVersion?</h2> |
| <p> |
| |
| |
| What is the <code>lintVersion</code> variable defined above? |
| |
| </p><p> |
| |
| Here's the top level build.gradle |
| </p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line">buildscript {</span> |
| <span class="line"> ext {</span> |
| <span class="line"> kotlinVersion = <span class="hljs-string">'1.4.32'</span></span> |
| <span class="line"></span> |
| <span class="line"> <span class="hljs-comment">// Current lint target: Studio 4.2 / AGP 7</span></span> |
| <span class="line"> <span class="hljs-comment">//gradlePluginVersion = '4.2.0-beta06'</span></span> |
| <span class="line"> <span class="hljs-comment">//lintVersion = '27.2.0-beta06'</span></span> |
| <span class="line"></span> |
| <span class="line"> <span class="hljs-comment">// Upcoming lint target: Arctic Fox / AGP 7</span></span> |
| <span class="line"> gradlePluginVersion = <span class="hljs-string">'7.0.0-alpha10'</span></span> |
| <span class="line"> lintVersion = <span class="hljs-string">'30.0.0-alpha10'</span></span> |
| <span class="line"> }</span> |
| <span class="line"></span> |
| <span class="line"> repositories {</span> |
| <span class="line"> google()</span> |
| <span class="line"> mavenCentral()</span> |
| <span class="line"> }</span> |
| <span class="line"> dependencies {</span> |
| <span class="line"> classpath <span class="hljs-string">"com.android.tools.build:gradle:<span class="hljs-variable">$gradlePluginVersion</span>"</span></span> |
| <span class="line"> classpath <span class="hljs-string">"org.jetbrains.kotlin:kotlin-gradle-plugin:<span class="hljs-variable">$kotlinVersion</span>"</span></span> |
| <span class="line"> }</span> |
| <span class="line">}</span></div></code></pre><p> |
| |
| The <code>$lintVersion</code> variable is defined on line 11. We don't technically |
| need to define the <code>$gradlePluginVersion</code> here or add it to the classpath on line 19, but that's done so that we can add the <code>lint</code> |
| plugin on the checks themselves, as well as for the other modules, |
| <code>:app</code> and <code>:library</code>, which do need it. |
| |
| </p><p> |
| |
| When you build lint checks, you're compiling against the Lint APIs |
| distributed on maven.google.com (which is referenced via <code>google()</code> in |
| Gradle files). These follow the Gradle plugin version numbers. |
| |
| </p><p> |
| |
| Therefore, you first pick which of lint's API you'd like to compile |
| against. You should use the latest available if possible. |
| |
| </p><p> |
| |
| Once you know the Gradle plugin version number, say 4.2.0-beta06, you |
| can compute the lint version number by simply adding <strong class="asterisk">23</strong> to the |
| major version of the gradle plugin, and leave everything the same: |
| |
| </p><p> |
| |
| <strong class="asterisk">lintVersion = gradlePluginVersion + 23.0.0</strong> |
| |
| </p><p> |
| |
| For example, 7 + 23 = 30, so AGP version <em class="asterisk">7.something</em> corresponds to |
| Lint version <em class="asterisk">30.something</em>. As another example; as of this writing the |
| current stable version of AGP is 4.1.2, so the corresponding version of |
| the Lint API is 27.1.2. |
| |
| </p><p> |
| |
| </p><div class="admonition tip">Why this arbitrary numbering — why can't lint just use the same |
| numbers? This is historical; lint (and various other sibling |
| libraries that lint depends on) was released earlier than the Gradle |
| plugin; it was up to version 22 or so. When we then shipped the |
| initial version of the Gradle plugin with Android Studio 1.0, we |
| wanted to start the numbering over from “1” for this brand new |
| artifact. However, some of the other libraries, like lint, couldn't |
| just start over at 1, so we continued incrementing their versions in |
| lockstep. Most users don't see this, but it's a wrinkle users of the |
| Lint API have to be aware of.</div> |
| |
| <p></p> |
| <a class="target" name=":libraryand:app"> </a><a class="target" name="example:samplelintcheckgithubproject/:libraryand:app"> </a><a class="target" name="toc3.4"> </a><h2>:library and :app</h2> |
| <p> |
| |
| |
| The <code>library</code> project depends on the lint check project, and will |
| package the lint checks as part of its payload. The <code>app</code> project |
| then depends on the <code>library</code>, and has some code which triggers |
| the lint check. This is there to demonstrate how lint checks can |
| be published and consumed, and this is described in detail in the |
| <a href="#publishingalintcheck">Publishing a Lint Check</a> chapter. |
| |
| </p> |
| <a class="target" name="lintcheckprojectlayout"> </a><a class="target" name="example:samplelintcheckgithubproject/lintcheckprojectlayout"> </a><a class="target" name="toc3.5"> </a><h2>Lint Check Project Layout</h2> |
| <p> |
| |
| |
| The lint checks source project is very simple |
| |
| </p><pre class="listing backtick"><code><span class="line"><span class="hljs-selector-tag">checks</span>/<span class="hljs-selector-tag">build</span><span class="hljs-selector-class">.gradle</span></span> |
| <span class="line"><span class="hljs-selector-tag">checks</span>/<span class="hljs-selector-tag">src</span>/<span class="hljs-selector-tag">main</span>/<span class="hljs-selector-tag">resources</span>/<span class="hljs-selector-tag">META-INF</span>/<span class="hljs-selector-tag">services</span>/<span class="hljs-selector-tag">com</span><span class="hljs-selector-class">.android</span><span class="hljs-selector-class">.tools</span><span class="hljs-selector-class">.lint</span><span class="hljs-selector-class">.client</span><span class="hljs-selector-class">.api</span><span class="hljs-selector-class">.IssueRegistry</span></span> |
| <span class="line"><span class="hljs-selector-tag">checks</span>/<span class="hljs-selector-tag">src</span>/<span class="hljs-selector-tag">main</span>/<span class="hljs-selector-tag">java</span>/<span class="hljs-selector-tag">com</span>/<span class="hljs-selector-tag">example</span>/<span class="hljs-selector-tag">lint</span>/<span class="hljs-selector-tag">checks</span>/<span class="hljs-selector-tag">SampleIssueRegistry</span><span class="hljs-selector-class">.kt</span></span> |
| <span class="line"><span class="hljs-selector-tag">checks</span>/<span class="hljs-selector-tag">src</span>/<span class="hljs-selector-tag">main</span>/<span class="hljs-selector-tag">java</span>/<span class="hljs-selector-tag">com</span>/<span class="hljs-selector-tag">example</span>/<span class="hljs-selector-tag">lint</span>/<span class="hljs-selector-tag">checks</span>/<span class="hljs-selector-tag">SampleCodeDetector</span><span class="hljs-selector-class">.kt</span></span> |
| <span class="line"><span class="hljs-selector-tag">checks</span>/<span class="hljs-selector-tag">src</span>/<span class="hljs-selector-tag">test</span>/<span class="hljs-selector-tag">java</span>/<span class="hljs-selector-tag">com</span>/<span class="hljs-selector-tag">example</span>/<span class="hljs-selector-tag">lint</span>/<span class="hljs-selector-tag">checks</span>/<span class="hljs-selector-tag">SampleCodeDetectorTest</span><span class="hljs-selector-class">.kt</span></span></code></pre><p> |
| |
| First is the build file, which we've discussed above. |
| |
| </p> |
| <a class="target" name="serviceregistration"> </a><a class="target" name="example:samplelintcheckgithubproject/serviceregistration"> </a><a class="target" name="toc3.6"> </a><h2>Service Registration</h2> |
| <p> |
| |
| |
| Then there's the service registration file. Notice how this file is in |
| the source set <code>src/main/resources/</code>, which means that Gradle will |
| treat it as a resource and will package it into the output jar, in the |
| <code>META-INF/services</code> folder. This is using the service-provider loading facility in the JDK to register a service lint can look up. The |
| key is the fully qualified name for lint's <code>IssueRegistry</code> class. |
| And the <strong class="asterisk">contents</strong> of that file is a single line, the fully |
| qualified name of the issue registry: |
| |
| </p><pre class="listing backtick"><code><span class="line"><span class="hljs-variable">$ </span>cat checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry</span> |
| <span class="line">com.example.lint.checks.SampleIssueRegistry</span></code></pre><p> |
| |
| (The service loader mechanism is understood by IntelliJ, so it will |
| correctly update the service file contents if the issue registry is |
| renamed etc.) |
| |
| </p><p> |
| |
| The service registration can contain more than one issue registry, |
| though there's usually no good reason for that, since a single issue |
| registry can provide multiple issues. |
| |
| </p> |
| <a class="target" name="issueregistry"> </a><a class="target" name="example:samplelintcheckgithubproject/issueregistry"> </a><a class="target" name="toc3.7"> </a><h2>IssueRegistry</h2> |
| <p> |
| |
| |
| Next we have the <code>IssueRegistry</code> linked from the service registration. |
| Lint will instantiate this class and ask it to provide a list of |
| issues. These are then merged with lint's other issues when lint |
| performs its analysis. |
| |
| </p><p> |
| |
| In its simplest form we'd only need to have the following code |
| in that file: |
| |
| </p><pre class="listing tilde"><code><span class="line"><span class="hljs-title">package</span> com.example.lint.checks</span> |
| <span class="line"><span class="hljs-keyword">import</span> com.android.tools.lint.client.api.IssueRegistry</span> |
| <span class="line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-type">SampleIssueRegistry</span> : <span class="hljs-type">IssueRegistry</span>() {</span> |
| <span class="line"> override val issues = listOf(<span class="hljs-type">SampleCodeDetector</span>.<span class="hljs-type">ISSUE</span>)</span> |
| <span class="line">}</span></span></code></pre><p> |
| |
| However, we're also providing some additional metadata about these lint |
|