blob: d48ce2c156e3866ba9dabba1731899aa6116016e [file] [log] [blame]
<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{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>
This chapter inlines all the API documentation into a single
long book, suitable for printing or reading on a tablet.
<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&nbsp; </span>Terminology</a><br>
<a href="#writingalintcheck:basics" class="level1"><span class="tocNumber">2&nbsp; </span>Writing a Lint Check: Basics</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/preliminaries" class="level2"><span class="tocNumber">2.1&nbsp; </span>Preliminaries</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#writingalintcheck:basics/preliminaries/%E2%80%9Clint?%E2%80%9D" class="level3"><span class="tocNumber">2.1.1&nbsp; </span>“Lint?”</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#writingalintcheck:basics/preliminaries/apistability" class="level3"><span class="tocNumber">2.1.2&nbsp; </span>API Stability</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#writingalintcheck:basics/preliminaries/kotlin" class="level3"><span class="tocNumber">2.1.3&nbsp; </span>Kotlin</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/concepts" class="level2"><span class="tocNumber">2.2&nbsp; </span>Concepts</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/clientapiversusdetectorapi" class="level2"><span class="tocNumber">2.3&nbsp; </span>Client API versus Detector API</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/creatinganissue" class="level2"><span class="tocNumber">2.4&nbsp; </span>Creating an Issue</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/textformat" class="level2"><span class="tocNumber">2.5&nbsp; </span>TextFormat</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/issueimplementation" class="level2"><span class="tocNumber">2.6&nbsp; </span>Issue Implementation</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/scopes" class="level2"><span class="tocNumber">2.7&nbsp; </span>Scopes</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/registeringtheissue" class="level2"><span class="tocNumber">2.8&nbsp; </span>Registering the Issue</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/implementingadetector:scanners" class="level2"><span class="tocNumber">2.9&nbsp; </span>Implementing a Detector: Scanners</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/detectorlifecycle" class="level2"><span class="tocNumber">2.10&nbsp; </span>Detector Lifecycle</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/scannerorder" class="level2"><span class="tocNumber">2.11&nbsp; </span>Scanner Order</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/implementingadetector:services" class="level2"><span class="tocNumber">2.12&nbsp; </span>Implementing a Detector: Services</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/scannerexample" class="level2"><span class="tocNumber">2.13&nbsp; </span>Scanner Example</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/analyzingkotlinandjavacode" class="level2"><span class="tocNumber">2.14&nbsp; </span>Analyzing Kotlin and Java Code</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#writingalintcheck:basics/analyzingkotlinandjavacode/uast" class="level3"><span class="tocNumber">2.14.1&nbsp; </span>UAST</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#writingalintcheck:basics/analyzingkotlinandjavacode/uastexample" class="level3"><span class="tocNumber">2.14.2&nbsp; </span>UAST Example</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#writingalintcheck:basics/analyzingkotlinandjavacode/lookingupuast" class="level3"><span class="tocNumber">2.14.3&nbsp; </span>Looking up UAST</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#writingalintcheck:basics/analyzingkotlinandjavacode/resolving" class="level3"><span class="tocNumber">2.14.4&nbsp; </span>Resolving</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#writingalintcheck:basics/analyzingkotlinandjavacode/psi" class="level3"><span class="tocNumber">2.14.5&nbsp; </span>PSI</a><br>
&nbsp;&nbsp;<a href="#writingalintcheck:basics/testing" class="level2"><span class="tocNumber">2.15&nbsp; </span>Testing</a><br>
<a href="#example:samplelintcheckgithubproject" class="level1"><span class="tocNumber">3&nbsp; </span>Example: Sample Lint Check GitHub Project</a><br>
&nbsp;&nbsp;<a href="#example:samplelintcheckgithubproject/projectlayout" class="level2"><span class="tocNumber">3.1&nbsp; </span>Project Layout</a><br>
&nbsp;&nbsp;<a href="#example:samplelintcheckgithubproject/:checks" class="level2"><span class="tocNumber">3.2&nbsp; </span>:checks</a><br>
&nbsp;&nbsp;<a href="#example:samplelintcheckgithubproject/lintversion?" class="level2"><span class="tocNumber">3.3&nbsp; </span>lintVersion?</a><br>
&nbsp;&nbsp;<a href="#example:samplelintcheckgithubproject/:libraryand:app" class="level2"><span class="tocNumber">3.4&nbsp; </span>:library and :app</a><br>
&nbsp;&nbsp;<a href="#example:samplelintcheckgithubproject/lintcheckprojectlayout" class="level2"><span class="tocNumber">3.5&nbsp; </span>Lint Check Project Layout</a><br>
&nbsp;&nbsp;<a href="#example:samplelintcheckgithubproject/serviceregistration" class="level2"><span class="tocNumber">3.6&nbsp; </span>Service Registration</a><br>
&nbsp;&nbsp;<a href="#example:samplelintcheckgithubproject/issueregistry" class="level2"><span class="tocNumber">3.7&nbsp; </span>IssueRegistry</a><br>
&nbsp;&nbsp;<a href="#example:samplelintcheckgithubproject/detector" class="level2"><span class="tocNumber">3.8&nbsp; </span>Detector</a><br>
&nbsp;&nbsp;<a href="#example:samplelintcheckgithubproject/detectortest" class="level2"><span class="tocNumber">3.9&nbsp; </span>Detector Test</a><br>
<a href="#publishingalintcheck" class="level1"><span class="tocNumber">4&nbsp; </span>Publishing a Lint Check</a><br>
&nbsp;&nbsp;<a href="#publishingalintcheck/android" class="level2"><span class="tocNumber">4.1&nbsp; </span>Android</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#publishingalintcheck/android/aarsupport" class="level3"><span class="tocNumber">4.1.1&nbsp; </span>AAR Support</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#publishingalintcheck/android/lintpublishconfiguration" class="level3"><span class="tocNumber">4.1.2&nbsp; </span>lintPublish Configuration</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#publishingalintcheck/android/localchecks" class="level3"><span class="tocNumber">4.1.3&nbsp; </span>Local Checks</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#publishingalintcheck/android/unpublishing" class="level3"><span class="tocNumber">4.1.4&nbsp; </span>Unpublishing</a><br>
<a href="#lintcheckunittesting" class="level1"><span class="tocNumber">5&nbsp; </span>Lint Check Unit Testing</a><br>
&nbsp;&nbsp;<a href="#lintcheckunittesting/creatingaunittest" class="level2"><span class="tocNumber">5.1&nbsp; </span>Creating a Unit Test</a><br>
&nbsp;&nbsp;<a href="#lintcheckunittesting/computingtheexpectedoutput" class="level2"><span class="tocNumber">5.2&nbsp; </span>Computing the Expected Output</a><br>
&nbsp;&nbsp;<a href="#lintcheckunittesting/testfiles" class="level2"><span class="tocNumber">5.3&nbsp; </span>Test Files</a><br>
&nbsp;&nbsp;<a href="#lintcheckunittesting/trimmingindents?" class="level2"><span class="tocNumber">5.4&nbsp; </span>Trimming indents?</a><br>
&nbsp;&nbsp;<a href="#lintcheckunittesting/dollarsinrawstrings" class="level2"><span class="tocNumber">5.5&nbsp; </span>Dollars in Raw Strings</a><br>
&nbsp;&nbsp;<a href="#lintcheckunittesting/quickfixes" class="level2"><span class="tocNumber">5.6&nbsp; </span>Quickfixes</a><br>
&nbsp;&nbsp;<a href="#lintcheckunittesting/librarydependenciesandstubs" class="level2"><span class="tocNumber">5.7&nbsp; </span>Library Dependencies and Stubs</a><br>
&nbsp;&nbsp;<a href="#lintcheckunittesting/binaryandcompiledsourcefiles" class="level2"><span class="tocNumber">5.8&nbsp; </span>Binary and Compiled Source Files</a><br>
&nbsp;&nbsp;<a href="#lintcheckunittesting/mydetectorisn'tinvokedfromatest!" class="level2"><span class="tocNumber">5.9&nbsp; </span>My Detector Isn't Invoked From a Test!</a><br>
<a href="#addingquickfixes" class="level1"><span class="tocNumber">6&nbsp; </span>Adding Quick Fixes</a><br>
&nbsp;&nbsp;<a href="#addingquickfixes/introduction" class="level2"><span class="tocNumber">6.1&nbsp; </span>Introduction</a><br>
&nbsp;&nbsp;<a href="#addingquickfixes/thelintfixbuilderclass" class="level2"><span class="tocNumber">6.2&nbsp; </span>The LintFix builder class</a><br>
&nbsp;&nbsp;<a href="#addingquickfixes/creatingalintfix" class="level2"><span class="tocNumber">6.3&nbsp; </span>Creating a LintFix</a><br>
&nbsp;&nbsp;<a href="#addingquickfixes/availablefixes" class="level2"><span class="tocNumber">6.4&nbsp; </span>Available Fixes</a><br>
&nbsp;&nbsp;<a href="#addingquickfixes/combiningfixes" class="level2"><span class="tocNumber">6.5&nbsp; </span>Combining Fixes</a><br>
&nbsp;&nbsp;<a href="#addingquickfixes/refactoringjavaandkotlincode" class="level2"><span class="tocNumber">6.6&nbsp; </span>Refactoring Java and Kotlin code</a><br>
&nbsp;&nbsp;<a href="#addingquickfixes/regularexpressionsandbackreferences" class="level2"><span class="tocNumber">6.7&nbsp; </span>Regular Expressions and Back References</a><br>
&nbsp;&nbsp;<a href="#addingquickfixes/emittingquickfixxmltoapplyonci" class="level2"><span class="tocNumber">6.8&nbsp; </span>Emitting quick fix XML to apply on CI</a><br>
<a href="#partialanalysis" class="level1"><span class="tocNumber">7&nbsp; </span>Partial Analysis</a><br>
&nbsp;&nbsp;<a href="#partialanalysis/about" class="level2"><span class="tocNumber">7.1&nbsp; </span>About</a><br>
&nbsp;&nbsp;<a href="#partialanalysis/theproblem" class="level2"><span class="tocNumber">7.2&nbsp; </span>The Problem</a><br>
&nbsp;&nbsp;<a href="#partialanalysis/overview" class="level2"><span class="tocNumber">7.3&nbsp; </span>Overview</a><br>
&nbsp;&nbsp;<a href="#partialanalysis/doesmydetectorneedwork?" class="level2"><span class="tocNumber">7.4&nbsp; </span>Does My Detector Need Work?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#partialanalysis/doesmydetectorneedwork?/catchingmistakes:blockingaccesstomainproject" class="level3"><span class="tocNumber">7.4.1&nbsp; </span>Catching Mistakes: Blocking Access to Main Project</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#partialanalysis/doesmydetectorneedwork?/catchingmistakes:simulatedappmodule" class="level3"><span class="tocNumber">7.4.2&nbsp; </span>Catching Mistakes: Simulated App Module</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#partialanalysis/doesmydetectorneedwork?/catchingmistakes:diffingresults" class="level3"><span class="tocNumber">7.4.3&nbsp; </span>Catching Mistakes: Diffing Results</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#partialanalysis/doesmydetectorneedwork?/catchingmistakes:remainingissues" class="level3"><span class="tocNumber">7.4.4&nbsp; </span>Catching Mistakes: Remaining Issues</a><br>
&nbsp;&nbsp;<a href="#partialanalysis/incidents" class="level2"><span class="tocNumber">7.5&nbsp; </span>Incidents</a><br>
&nbsp;&nbsp;<a href="#partialanalysis/constraints" class="level2"><span class="tocNumber">7.6&nbsp; </span>Constraints</a><br>
&nbsp;&nbsp;<a href="#partialanalysis/incidentlintmaps" class="level2"><span class="tocNumber">7.7&nbsp; </span>Incident LintMaps</a><br>
&nbsp;&nbsp;<a href="#partialanalysis/modulelintmaps" class="level2"><span class="tocNumber">7.8&nbsp; </span>Module LintMaps</a><br>
&nbsp;&nbsp;<a href="#partialanalysis/optimizations" class="level2"><span class="tocNumber">7.9&nbsp; </span>Optimizations</a><br>
<a href="#frequentlyaskedquestions" class="level1"><span class="tocNumber">8&nbsp; </span>Frequently Asked Questions</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//mydetectorcallbacksaren'tinvoked" class="level3"><span class="tocNumber">8.0.1&nbsp; </span>My detector callbacks aren't invoked</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//mylintcheckworksfromtheunittestbutnotintheide" class="level3"><span class="tocNumber">8.0.2&nbsp; </span>My lint check works from the unit test but not in the IDE</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//visitannotationusageisn'tcalledforannotations" class="level3"><span class="tocNumber">8.0.3&nbsp; </span><code>visitAnnotationUsage</code> isn't called for annotations</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdoicheckifauastorpsielementisforjavaorkotlin?" class="level3"><span class="tocNumber">8.0.4&nbsp; </span>How do I check if a UAST or PSI element is for Java or Kotlin?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//whatifineedapsielementandihaveauelement?" class="level3"><span class="tocNumber">8.0.5&nbsp; </span>What if I need a <code>PsiElement</code> and I have a <code>UElement</code> ?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdoigettheumethodforapsimethod?" class="level3"><span class="tocNumber">8.0.6&nbsp; </span>How do I get the <code>UMethod</code> for a <code>PsiMethod</code> ?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdogetajavaevaluator?" class="level3"><span class="tocNumber">8.0.7&nbsp; </span>How do get a <code>JavaEvaluator</code> ?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdoicheckwhetheranelementisinternal?" class="level3"><span class="tocNumber">8.0.8&nbsp; </span>How do I check whether an element is internal?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//iselementinline,sealed,operator,infix,suspend,data?" class="level3"><span class="tocNumber">8.0.9&nbsp; </span>Is element inline, sealed, operator, infix, suspend, data?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdoilookupaclassifihaveitsfullyqualifiedname?" class="level3"><span class="tocNumber">8.0.10&nbsp; </span>How do I look up a class if I have its fully qualified name?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdoilookupaclassifihaveapsitype?" class="level3"><span class="tocNumber">8.0.11&nbsp; </span>How do I look up a class if I have a PsiType?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdoilookuphierarhcyannotationsforanelement?" class="level3"><span class="tocNumber">8.0.12&nbsp; </span>How do I look up hierarhcy annotations for an element?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdoilookupifaclassisasubclassofanother?" class="level3"><span class="tocNumber">8.0.13&nbsp; </span>How do I look up if a class is a subclass of another?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdoiknowwhichparameteracallargumentcorrespondsto?" class="level3"><span class="tocNumber">8.0.14&nbsp; </span>How do I know which parameter a call argument corresponds to?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howcanmylintcheckstargettwodifferentversionsoflint?" class="level3"><span class="tocNumber">8.0.15&nbsp; </span>How can my lint checks target two different versions of lint?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//howdoicheckoutthecurrentlintsourcecode?" class="level3"><span class="tocNumber">8.0.16&nbsp; </span>How do I check out the current lint source code?</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#frequentlyaskedquestions//wheredoifindexamplesoflintchecks?" class="level3"><span class="tocNumber">8.0.17&nbsp; </span>Where do I find examples of lint checks?</a><br>
<a href="#appendix:recentchanges" class="level1"><span class="tocNumber">9&nbsp; </span>Appendix: Recent Changes</a><br>
<a href="#appendix:environmentvariablesandsystemproperties" class="level1"><span class="tocNumber">10&nbsp; </span>Appendix: Environment Variables and System Properties</a><br>
&nbsp;&nbsp;<a href="#appendix:environmentvariablesandsystemproperties/environmentvariables" class="level2"><span class="tocNumber">10.1&nbsp; </span>Environment Variables</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#appendix:environmentvariablesandsystemproperties/environmentvariables/detectorconfigurationvariables" class="level3"><span class="tocNumber">10.1.1&nbsp; </span>Detector Configuration Variables</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#appendix:environmentvariablesandsystemproperties/environmentvariables/lintconfigurationvariables" class="level3"><span class="tocNumber">10.1.2&nbsp; </span>Lint Configuration Variables</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#appendix:environmentvariablesandsystemproperties/environmentvariables/lintdevelopmentvariables" class="level3"><span class="tocNumber">10.1.3&nbsp; </span>Lint Development Variables</a><br>
&nbsp;&nbsp;<a href="#appendix:environmentvariablesandsystemproperties/systemproperties" class="level2"><span class="tocNumber">10.2&nbsp; </span>System Properties</a><br>
</p></div><a class="target" name="terminology">&nbsp;</a><a class="target" name="terminology">&nbsp;</a><a class="target" name="toc1">&nbsp;</a><h1>Terminology</h1>
You don't need to read this up front and understand everything, but
this is hopefully a handy reference to return to.
In alphabetical order:
</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.
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.
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.
Typically lint cares about which <strong class="asterisk">set</strong> of scopes apply,
so most of the APIs take an <code>EnumSet&lt; Scope&gt;</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.
<a class="target" name="writingalintcheck:basics">&nbsp;</a><a class="target" name="writingalintcheck:basics">&nbsp;</a><a class="target" name="toc2">&nbsp;</a><h1>Writing a Lint Check: Basics</h1>
<a class="target" name="preliminaries">&nbsp;</a><a class="target" name="writingalintcheck:basics/preliminaries">&nbsp;</a><a class="target" name="toc2.1">&nbsp;</a><h2>Preliminaries</h2>
(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.)
<a class="target" name="%E2%80%9Clint?%E2%80%9D">&nbsp;</a><a class="target" name="writingalintcheck:basics/preliminaries/%E2%80%9Clint?%E2%80%9D">&nbsp;</a><a class="target" name="toc2.1.1">&nbsp;</a><h3>“Lint?”</h3>
The <code>lint</code> tool shipped with the C compiler and provided additional
static analysis of C code beyond what the compiler checked.
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.
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</a> chapter.
We're planning to rename lint to reflect this new role, so we are
looking for good name suggestions.
<a class="target" name="apistability">&nbsp;</a><a class="target" name="writingalintcheck:basics/preliminaries/apistability">&nbsp;</a><a class="target" name="toc2.1.2">&nbsp;</a><h3>API Stability</h3>
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.
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).
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
(, 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.
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.
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
<a class="target" name="kotlin">&nbsp;</a><a class="target" name="writingalintcheck:basics/preliminaries/kotlin">&nbsp;</a><a class="target" name="toc2.1.3">&nbsp;</a><h3>Kotlin</h3>
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
<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.
<li class="asterisk"><strong class="asterisk">Compatibility</strong>: We may add additional parameters over time. It
isn't practical to add @JvmOverloads on everything.
<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).
<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">""</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
</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>
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.
<a class="target" name="concepts">&nbsp;</a><a class="target" name="writingalintcheck:basics/concepts">&nbsp;</a><a class="target" name="toc2.2">&nbsp;</a><h2>Concepts</h2>
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.
Each instance that it finds is called an ”incident“.
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.
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.
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.
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><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>
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.
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).
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.
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><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>
<a class="target" name="clientapiversusdetectorapi">&nbsp;</a><a class="target" name="writingalintcheck:basics/clientapiversusdetectorapi">&nbsp;</a><a class="target" name="toc2.3">&nbsp;</a><h2>Client API versus Detector API</h2>
Lint's API has two halves:
<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.
<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>
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:
<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.
<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.
<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 network output to</span>
<span class="line"> // ensure stable version suggestions in the tests</span>
<span class="line"> .networkData("", ""</span>
<span class="line"> + "<span class="hljs-comment">&lt;!--?xml version='1.0' encoding='UTF-8'?--&gt;</span>\n"</span>
<span class="line"> + "<span class="hljs-tag">&lt;<span class="hljs-name">metadata</span>&gt;</span>\n"</span>
<span class="line"> + " <span class="hljs-tag">&lt;<span class="hljs-name"></span>&gt;</span>"</span>
<span class="line"> + "<span class="hljs-tag">&lt;/<span class="hljs-name"></span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">metadata</span>&gt;</span>")</span>
<span class="line"> .networkData("", ""</span>
<span class="line"> + "<span class="hljs-comment">&lt;!--?xml version='1.0' encoding='UTF-8'?--&gt;</span>\n"</span>
<span class="line"> + "<span class="hljs-tag">&lt;<span class="hljs-name"></span>&gt;</span>\n"</span>
<span class="line"> + " <span class="hljs-tag">&lt;<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>\"/"&gt;</span>\n"</span>
<span class="line"> + "<span class="hljs-tag">&lt;/<span class="hljs-name">gradle</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name"></span>&gt;</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><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><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>
<a class="target" name="creatinganissue">&nbsp;</a><a class="target" name="writingalintcheck:basics/creatinganissue">&nbsp;</a><a class="target" name="toc2.4">&nbsp;</a><h2>Creating an Issue</h2>
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.
<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>.
By convention, issues are registered inside the companion object of the
corresponding detector, but that is not required.
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">""</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.
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.
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).
The <code>Issue</code> provides metadata about a type of problem.
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.)
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.
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.
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.
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.
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.
However, we do need to add line continuations — those are the trailing
\'s at the end of the lines.
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.
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.
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.
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.
<a class="target" name="textformat">&nbsp;</a><a class="target" name="writingalintcheck:basics/textformat">&nbsp;</a><a class="target" name="toc2.5">&nbsp;</a><h2>TextFormat</h2>
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>
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.
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.
<a class="target" name="issueimplementation">&nbsp;</a><a class="target" name="writingalintcheck:basics/issueimplementation">&nbsp;</a><a class="target" name="toc2.6">&nbsp;</a><h2>Issue Implementation</h2>
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.
Normally, the <code>Implementation</code> provides two things:
<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>.
<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>
<a class="target" name="scopes">&nbsp;</a><a class="target" name="writingalintcheck:basics/scopes">&nbsp;</a><a class="target" name="toc2.7">&nbsp;</a><h2>Scopes</h2>
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.
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.
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.
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.
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.
<a class="target" name="registeringtheissue">&nbsp;</a><a class="target" name="writingalintcheck:basics/registeringtheissue">&nbsp;</a><a class="target" name="toc2.8">&nbsp;</a><h2>Registering the Issue</h2>
Once you've created your issue, you need to provide it from
an <code>IssueRegistry</code>.
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></span>
<span class="line"><span class="hljs-keyword">import</span></span>
<span class="line"><span class="hljs-keyword">import</span></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">// / 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.
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
The <strong class="asterisk"><code>minApi</code></strong> property records the oldest lint API level this check
has been tested with.
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).
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
The last step towards making the lint check available is to make
the <code>IssueRegistry</code> known via the service loader mechanism.
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
<a class="target" name="implementingadetector:scanners">&nbsp;</a><a class="target" name="writingalintcheck:basics/implementingadetector:scanners">&nbsp;</a><a class="target" name="toc2.9">&nbsp;</a><h2>Implementing a Detector: Scanners</h2>
We've finally come to the main task with writing a lint check:
implementing the <strong class="asterisk"><code>Detector</code></strong>.
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">, 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.
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.
For example:
<li class="asterisk">When implementing <strong class="asterisk"><code>XmlScanner</code></strong>, in an XML element you can be
called back
<li class="minus">when any of a set of given tags are declared (<code>visitElement</code>)
<li class="minus">when any of a set of named attributes are declared
<li class="minus">and you can perform your own document traversal via <code>visitDocument</code>
</li><li class="asterisk">When implementing <strong class="asterisk"><code>SourceCodeScanner</code></strong>, in Kotlin and Java files
you can be called back
<li class="minus">When a method of a given name is invoked (<code>getApplicableMethodNames</code>
and <code>visitMethodCall</code>)
<li class="minus">When a class of the given type is instantiated
(<code>getApplicableConstructorTypes</code> and <code>visitConstructor</code>)
<li class="minus">When a new class is declared which extends (possibly indirectly)
a given class or interface (<code>applicableSuperClasses</code> and
<li class="minus">When annotated elements are referenced or combined
(<code>applicableAnnotations</code> and <code>visitAnnotationUsage</code>)
<li class="minus">When any AST nodes of given types appear (<code>getApplicableUastTypes</code>
and <code>createUastHandler</code>)
</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
<li class="minus">when a method is invoked for a particular owner
(<code>getApplicableCallOwners</code> and <code>checkCall</code>
<li class="minus">when a given bytecode instruction occurs
(<code>getApplicableAsmNodeTypes</code> and <code>checkInstruction</code>)
<li class="minus">like with XmlScanner's <code>visitDocument</code>, you can perform your own
ASM bytecode iteration via <code>checkClass</code>.
</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><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><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>
<a class="target" name="detectorlifecycle">&nbsp;</a><a class="target" name="writingalintcheck:basics/detectorlifecycle">&nbsp;</a><a class="target" name="toc2.10">&nbsp;</a><h2>Detector Lifecycle</h2>
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
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
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.
<a class="target" name="scannerorder">&nbsp;</a><a class="target" name="writingalintcheck:basics/scannerorder">&nbsp;</a><a class="target" name="toc2.11">&nbsp;</a><h2>Scanner Order</h2>
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.
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.
Here's lint's defined order:
</p><ol start="1">
<li class="number">Android Manifest
<li class="number">Android resources XML files (alphabetical by folder type, so for
example layouts are processed before value files like translations)
<li class="number">Kotlin and Java files
<li class="number">Bytecode (local <code>.class</code> files and library <code>.jar</code> files)
<li class="number">Gradle files
<li class="number">Other files
<li class="number">ProGuard files
<li class="number">Property Files</li></ol>
Similarly, lint will always process libraries before the modules
that depend on them.
</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>
<a class="target" name="implementingadetector:services">&nbsp;</a><a class="target" name="writingalintcheck:basics/implementingadetector:services">&nbsp;</a><a class="target" name="toc2.12">&nbsp;</a><h2>Implementing a Detector: Services</h2>
In addition to the scanners, lint provides a number of services
to make implementation simpler. These include
<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.
<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>.
<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.
<li class="asterisk"><strong class="asterisk"><code>DataFlowAnalyzer</code></strong>: Data flow analysis within a method.
<li class="asterisk">For Android analysis, there are several other important services,
like the <code>ResourceRepository</code> and the <code>ResourceEvaluator</code>.
<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>
<a class="target" name="scannerexample">&nbsp;</a><a class="target" name="writingalintcheck:basics/scannerexample">&nbsp;</a><a class="target" name="toc2.13">&nbsp;</a><h2>Scanner Example</h2>
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>&lt;bitmap&gt;</code> tag it will report that <code>&lt;vector&gt;</code> should
be used instead:
</p><pre class="listing tilde"><code><div class=" linenumbers"><span class="line"><span class="hljs-keyword">import</span></span>
<span class="line"><span class="hljs-keyword">import</span></span>
<span class="line"><span class="hljs-keyword">import</span></span>
<span class="line"><span class="hljs-keyword">import</span></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 `&lt;vector&gt;` instead of `&lt;bitmap&gt;`"</span>)</span>
<span class="line"> .at(element)</span>
<span class="line"></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.getLocation(element),</span>
<span class="line"> <span class="hljs-string">"Use `&lt;vector&gt;` instead of `&lt;bitmap&gt;`"</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.getLocation(element),</span>
<span class="line"> <span class="hljs-string">"Use `&lt;vector&gt;` instead of `&lt;bitmap&gt;`"</span>))</span></div></code></pre>
<a class="target" name="analyzingkotlinandjavacode">&nbsp;</a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode">&nbsp;</a><a class="target" name="toc2.14">&nbsp;</a><h2>Analyzing Kotlin and Java Code</h2>
<a class="target" name="uast">&nbsp;</a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/uast">&nbsp;</a><a class="target" name="toc2.14.1">&nbsp;</a><h3>UAST</h3>
To analyze Kotlin and Java code, lint offers an abstract syntax tree,
or ”AST“, for the code.
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.
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,
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.
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>.
<svg class="diagram" xmlns="" 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><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>
<a class="target" name="uastexample">&nbsp;</a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/uastexample">&nbsp;</a><a class="target" name="toc2.14.2">&nbsp;</a><h3>UAST Example</h3>
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.
Line 1 says we want to have line 3 called whenever lint comes across a
method to <code>setRepeating</code>.
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.
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&lt;string&gt; = 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">""</span>) &amp;&amp;</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 &lt; 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">, 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">&nbsp;</a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/lookingupuast">&nbsp;</a><a class="target" name="toc2.14.3">&nbsp;</a><h3>Looking up UAST</h3>
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>.
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,
<a class="target" name="resolving">&nbsp;</a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/resolving">&nbsp;</a><a class="target" name="toc2.14.4">&nbsp;</a><h3>Resolving</h3>
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><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>
<a class="target" name="psi">&nbsp;</a><a class="target" name="writingalintcheck:basics/analyzingkotlinandjavacode/psi">&nbsp;</a><a class="target" name="toc2.14.5">&nbsp;</a><h3>PSI</h3>
PSI is short for ”Program Structure Interface“, and is IntelliJ's AST
abstraction used for all language modeling in the IDE.
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.)
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.
However, there are a few scenarios where we have to use PSI.
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><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
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.
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.
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.
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><div class="admonition tip">You can find additional documentation from JetBrains for both
<a href="">PSI</a> and
<a href="">UAST</a>.
Just note that their documentation is aimed at IDE plugin developers
rather than lint developers.</div>
<a class="target" name="testing">&nbsp;</a><a class="target" name="writingalintcheck:basics/testing">&nbsp;</a><a class="target" name="toc2.15">&nbsp;</a><h2>Testing</h2>
Writing unit tests for the lint check is important, and this is covered
in detail in the dedicated <a href="#lintcheckunittesting">unit testing</a>
<a class="target" name="example:samplelintcheckgithubproject">&nbsp;</a><a class="target" name="example:samplelintcheckgithubproject">&nbsp;</a><a class="target" name="toc3">&nbsp;</a><h1>Example: Sample Lint Check GitHub Project</h1>
The <a href=""></a><a href="" class="url"></a>
GitHub project provides a sample lint check which shows a working
This chapter walks through that sample project and explains
what and why.
<a class="target" name="projectlayout">&nbsp;</a><a class="target" name="example:samplelintcheckgithubproject/projectlayout">&nbsp;</a><a class="target" name="toc3.1">&nbsp;</a><h2>Project Layout</h2>
Here's the project layout of the sample project:
<svg class="diagram" xmlns="" 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>
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.
<a class="target" name=":checks">&nbsp;</a><a class="target" name="example:samplelintcheckgithubproject/:checks">&nbsp;</a><a class="target" name="toc3.2">&nbsp;</a><h2>:checks</h2>
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: ''</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>
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">"<span class="hljs-variable">$lintVersion</span>"</span></span>
<span class="line"> compileOnly <span class="hljs-string">"<span class="hljs-variable">$lintVersion</span>"</span></span>
<span class="line"> testImplementation <span class="hljs-string">"<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.
<a class="target" name="lintversion?">&nbsp;</a><a class="target" name="example:samplelintcheckgithubproject/lintversion?">&nbsp;</a><a class="target" name="toc3.3">&nbsp;</a><h2>lintVersion?</h2>
What is the <code>lintVersion</code> variable defined above?
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">"<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.
When you build lint checks, you're compiling against the Lint APIs
distributed on (which is referenced via <code>google()</code> in
Gradle files). These follow the Gradle plugin version numbers.
Therefore, you first pick which of lint's API you'd like to compile
against. You should use the latest available if possible.
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:
<strong class="asterisk">lintVersion = gradlePluginVersion + 23.0.0</strong>
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><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>
<a class="target" name=":libraryand:app">&nbsp;</a><a class="target" name="example:samplelintcheckgithubproject/:libraryand:app">&nbsp;</a><a class="target" name="toc3.4">&nbsp;</a><h2>:library and :app</h2>
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.
<a class="target" name="lintcheckprojectlayout">&nbsp;</a><a class="target" name="example:samplelintcheckgithubproject/lintcheckprojectlayout">&nbsp;</a><a class="target" name="toc3.5">&nbsp;</a><h2>Lint Check Project Layout</h2>
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.
<a class="target" name="serviceregistration">&nbsp;</a><a class="target" name="example:samplelintcheckgithubproject/serviceregistration">&nbsp;</a><a class="target" name="toc3.6">&nbsp;</a><h2>Service Registration</h2>
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/</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.)
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.
<a class="target" name="issueregistry">&nbsp;</a><a class="target" name="example:samplelintcheckgithubproject/issueregistry">&nbsp;</a><a class="target" name="toc3.7">&nbsp;</a><h2>IssueRegistry</h2>
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.
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></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