[
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\n## Introduction\n\nDiversity and inclusion make our community strong. We encourage participation from the most varied and diverse backgrounds possible and want to be very clear about where we stand.\n\nOur goal is to maintain a safe, helpful and friendly community for everyone, regardless of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other defining characteristic.\n\nThis code and related procedures also apply to unacceptable behavior occurring outside the scope of community activities, in all community venues (online and in-person) as well as in all one-on-one communications, and anywhere such behavior has the potential to adversely affect the safety and well-being of community members.\n\nFor more information on our code of conduct, please visit [https://slackhq.github.io/code-of-conduct](https://slackhq.github.io/code-of-conduct)\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributors Guide\n\nNote that this project is considered READ-ONLY. You are welcome to discuss or ask questions in the\ndiscussions section of the repo, but we do not normally accept external contributions without prior\ndiscussion.\n\n## Development\n\nCheck out this repo with Android Studio or IntelliJ. It's a standard gradle project and\nconventional to check out.\n\nThe primary project is `slack-lint`.\n\nKotlin should be used for more idiomatic use with lint APIs.\n\n## Setup\n\nBe sure your devel environment has `ANDROID_HOME` defined or you'll have trouble running tests\nthat require the Android SDK. If you've added it and still seeing the error about not having it\ndefined while running tests, try closing and re-opening Android Studio.\n\n## Lint Documentation\n\n[The Android Lint API Guide](https://googlesamples.github.io/android-custom-lint-rules/book.html) provides an excellent overview of lint's purpose, how it works, and how to author custom checks.\n\n## Lint Guidelines\n- Limited scopes. Remember this will run in a slow build step or during the IDE, performance matters!\n    - If your check only matters for java or kotlin, only run on appropriate files\n    - Use the smallest necessary scope. Avoid tree walking through the AST if it can be avoided, there\n      are usually more appropriate hooks.\n- Use `UElementHandler` (via overriding `createUastHandler()`) rather than overriding `Detector`\n  callback methods. `Detector` callback methods tend only to be useful for tricky scenarios, like\n  annotated elements. For basic `UElement` types it's best to just use `UElementHandler` as it affords\n  a standard API and is easy to conditionally avoid nested parsing.\n- For testing, prefer writing source stubs directly in the test rather than extract individual files\n  in `resources` for stubs. Stubs in resources add friction for source glancing and tedious to\n  maintain, and should only be used for extremely complex source files.\n- Use our `implementation<*Detector>()` helper functions for wiring your `Issue` information. This\n  is important because it will help ensure your check works in both command line and in the IDE.\n\n## Maintainers\n\nThere are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md).\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "###  Summary\n\nDescribe the goal of this PR. Mention any related Issue numbers. Please note that we do not normally\naccept new externally-contributed PRs to this repo unless it's something Slack-relevant and came \nup in prior discussion\n\n### Requirements (place an `x` in each `[ ]`)\n\n* [ ] I've read and understood the [Contributing Guidelines](https://github.com/{project_slug}/blob/master/.github/contributing.md) and have done my best effort to follow them.\n* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).\n\n> The following point can be removed after setting up CI (such as Travis) with coverage reports (such as Codecov)\n\n* [ ] I've written tests to cover the new code and functionality included in this PR.\n\n> The following point can be removed after setting up a CLA reporting tool such as cla-assistant.io\n\n* [ ] I've read, agree to, and signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/{project_slug}).\n"
  },
  {
    "path": ".github/maintainers_guide.md",
    "content": "# Maintainers Guide\n\nThis document describes tools, tasks and workflow that one needs to be familiar with in order to effectively maintain\nthis project. If you use this package within your own software as is but don't plan on modifying it, this guide is\n**not** for you.\n\n## Tools (optional)\n\n> Are there any build tools, dependencies, or other programs someone maintaining this project\n> needs to be familiar with?\n\n## Tasks\n\n### Testing\n\n> How do you run the tests?\n\n### Generating Documentation (optional)\n\n> If the documentation is generated from source, how does someone run the generation?\n> Are the docs published on a website (GitHub Pages)?\n\n### Releasing\n\n> A description of the process to make a release for this project. Do not share any secrets here.\n\n## Workflow\n\n### Versioning and Tags\n\n> Does this project use semver? What does the numbering system look like? Are releases tagged in git?\n\n### Branches\n\n> Describe any specific branching workflow. For example:\n> `master` is where active development occurs.\n> Long running branches named feature branches are occasionally created for collaboration on a feature that has a large scope (because everyone cannot push commits to another person's open Pull Request)\n> At some point in the future after a major version increment, there may be maintenance branches\n> for older major versions.\n\n### Issue Management\n\nLabels are used to run issues through an organized workflow. Here are the basic definitions:\n\n*  `bug`: A confirmed bug report. A bug is considered confirmed when reproduction steps have been\n   documented and the issue has been reproduced.\n*  `enhancement`: A feature request for something this package might not already do.\n*  `docs`: An issue that is purely about documentation work.\n*  `tests`: An issue that is purely about testing work.\n*  `needs feedback`: An issue that may have claimed to be a bug but was not reproducible, or was otherwise missing some information.\n*  `discussion`: An issue that is purely meant to hold a discussion. Typically the maintainers are looking for feedback in this issues.\n*  `question`: An issue that is like a support request because the user's usage was not correct.\n*  `semver:major|minor|patch`: Metadata about how resolving this issue would affect the version number.\n*  `security`: An issue that has special consideration for security reasons.\n*  `good first contribution`: An issue that has a well-defined relatively-small scope, with clear expectations. It helps when the testing approach is also known.\n*  `duplicate`: An issue that is functionally the same as another issue. Apply this only if you've linked the other issue by number.\n\n> You may want to add more labels for subsystems of your project, depending on how complex it is.\n\n**Triage** is the process of taking new issues that aren't yet \"seen\" and marking them with a basic\nlevel of information with labels. An issue should have **one** of the following labels applied:\n`bug`, `enhancement`, `question`, `needs feedback`, `docs`, `tests`, or `discussion`.\n\nIssues are closed when a resolution has been reached. If for any reason a closed issue seems\nrelevant once again, reopening is great and better than creating a duplicate issue.\n\n## Everything else\n\nWhen in doubt, find the other maintainers and ask.\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  # Only run push on main\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - '**/*.md'\n  # Always run on PRs\n  pull_request:\n    branches: [ main ]\n  merge_group:\n\nconcurrency:\n  group: 'ci-${{ github.event.merge_group.head_ref || github.head_ref }}-${{ github.workflow }}'\n  cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Install JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: '23'\n\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v6\n\n      - name: Build and run tests\n        id: gradle\n        run: ./gradlew check --quiet\n\n      - uses: mikepenz/action-junit-report@v6\n        if: success() || failure()\n        with:\n          report_paths: '**/build/test-results/test/TEST-*.xml'\n\n      - name: (Fail-only) Upload build reports\n        if: failure()\n        uses: actions/upload-artifact@v7\n        with:\n          name: reports\n          path: |\n            **/build/reports/**\n\n      - name: Publish snapshot (main branch only)\n        if: github.repository == 'slackhq/slack-lint-checks' && github.ref == 'refs/heads/main'\n        run: ./gradlew publish -PmavenCentralUsername=${{ secrets.SONATYPEUSERNAME }} -PmavenCentralPassword=${{ secrets.SONATYPEPASSWORD }}\n"
  },
  {
    "path": ".github/workflows/increment_version.sh",
    "content": "#!/usr/bin/env bash\n\n# Target project to update, required\nTARGET=$1\n# Version to increment to, optional. If blank or not specified, will auto-increment.\nREQUESTED_VERSION=$2\nsource .github/workflows/scriptUtil.sh\n# Parse the current version, strip leading zeros, increment\nCURRENT_VERSION=$(getProperty 'VERSION_NAME' \"$TARGET\"/gradle.properties)\n\n# Export the coordinates while we're at it for later use in publish.yml\n# Group is always in our root dir\nGROUP=$(getProperty 'GROUP' gradle.properties)\nARTIFACT=$(getProperty 'POM_ARTIFACT_ID' \"$TARGET\"/gradle.properties)\n\nSTRIPPED_CURRENT=$(echo \"$CURRENT_VERSION\" | sed 's/^0*//')\nif [[ \"$REQUESTED_VERSION\" != \"\" ]]; then\n  NEW_VERSION=$REQUESTED_VERSION\nelse\n  ((STRIPPED_CURRENT++))\n  NEW_VERSION=$(printf \"%05d\\n\" $STRIPPED_CURRENT)\nfi\nsed -i -e \"s/${CURRENT_VERSION}/${NEW_VERSION}/g\" \"$TARGET\"/gradle.properties\necho \"current: $CURRENT_VERSION\"\necho \"CURRENT_VERSION=$CURRENT_VERSION\" >> $GITHUB_ENV\necho \"new: $NEW_VERSION\"\necho \"NEW_VERSION=$NEW_VERSION\" >> $GITHUB_ENV\n# We just use the artifact ID in the android repo for the coordinate\necho \"COORDINATES=$ARTIFACT\" >> $GITHUB_ENV\n"
  },
  {
    "path": ".github/workflows/renovate.yml",
    "content": "name: Renovate\n\non:\n  schedule:\n    - cron: \"0 8 * * *\" # 8am daily\n  workflow_dispatch:\n\njobs:\n  renovate:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Self-hosted Renovate\n        uses: renovatebot/github-action@v32.190.6\n        with:\n          configurationFile: renovate.json\n          token: ${{ secrets.SLACKHQ_MBR_GITHUB_TOKEN }}"
  },
  {
    "path": ".github/workflows/scriptUtil.sh",
    "content": "# Source this file to use its functions\n\n# Gets a property out of a .properties file\n# usage: getProperty $key $filename\nfunction getProperty() {\n    grep \"${1}\" \"$2\" | cut -d'=' -f2\n}\n"
  },
  {
    "path": ".gitignore",
    "content": ".gradle\n.kotlin/\nlocal.properties\n.idea\n!/.idea/codeStyles\n!/.idea/inspectionProfiles\n!/.idea/icon.png\n*.iml\n*.so\n.DS_Store\nbuild\ncaptures\nversion.properties\nenv\nnode_modules\nreports\n.cxx\n\n# IDE-generated dir\nout/\n\n# We have one gitignore but project generation often generates extra ones. Ignore those\n*/**/.gitignore"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Changelog\n=========\n\n**Unreleased**\n--------------\n\n0.11.1\n------\n\n_2025-10-10_\n\n- **Fix**: Allow sealed classes to be Moshi-compatible.\n- **Fix**: Allow primitives in collections to be Moshi-compatible.\n- **Fix**: Allow sealed interfaces to be Moshi-compatible.\n- **Fix**: Allow enums to be Moshi-compatible.\n- Update Kotlin to `2.2.10`.\n- Update plugin lint to `8.13.0`.\n- Update dependency gradle to `9.1.0`.\n- Update various build plugins and GitHub Actions.\n\n0.11.0\n------\n\n_2025-07-17_\n\n- **New**: Add `CircuitScreenDataClassDetector` check to ensure that [Circuit](https://github.com/slackhq/circuit) `Screen` classes are data classes or data objects.\n- **Fix**: Enable the `JsonInflaterMoshiCompatibilityDetector` in slack-lint's lint registry.\n- **Fix**: _Actually_ fix \"You must override visitCallExpression (and don't call super.visitCallExpression!)\" error.\n- **Change**: Disable `AlwaysNullReadOnlyVariableDetector` for now as this was causing the issue above it seems and we haven't had a change to investigate further.\n- Build against lint `31.12.0-alpha09`.\n\n0.10.1\n------\n\n_2025-07-14_\n\n- Fix \"You must override visitCallExpression (and don't call super.visitCallExpression!)\" error.\n\n0.10.0\n------\n\n_2025-06-30_\n\n- **New:** Add `JsonInflaterMoshiCompatibilityDetector` check. This lint is only usable to slack's internal repo.\n- **New:** Add `RxObservableEmitDetector` to ensure that `rxObservable`/`rxFlowable` lambda expressions call `send`/`trySend`.\n- **New:** Add `AlwaysNullReadOnlyVariableDetector` to lint against read-only variables always initialized to null.\n- Allow `suspend` Retrofit functions to return `Unit` if annotated with `@AllowUnitResult`.\n- Update lint to `31.12.0-alpha07`.\n- Only test against K2 UAST now.\n- Test against Retrofit `3.0.0`.\n\nSpecial thanks to [@henni99](https://github.com/henni99) for contributing to this release!\n\n0.9.0\n-----\n\n_2025-03-27_\n\n- **New**: Add `TestParameterSiteTarget` check to protect against https://github.com/google/TestParameterInjector/issues/49.\n- **New**: Add `NullableConcurrentHashMap` check to protect against putting null keys or values into `ConcurrentHashMap`s.\n- Add mockito-kotlin mock/spy functions to default `DoNotMock` checks.\n- Add `java.util.Calendar` to `DenyListedApiDetector`.\n- Don't require `ExceptionMessage` lint in tests.\n- Update EitherNet checks to EitherNet 2.0.\n- Raise lint registry API version to `16` (`8.7.0-alpha04`).\n- Build against Kotlin `2.1.20`.\n- Build against lint `31.10.0-alpha03`.\n\nSpecial thanks to [@mformetal](https://github.com/mformetal) and [@jbduncan](https://github.com/jbduncan) for contributing to this release!\n\n0.8.2\n-----\n\n_2024-10-14_\n\n- **Enhancement**: Handle `@Multipart` and `@Part` annotations in Retrofit lints.\n\n0.8.1\n-----\n\n_2024-10-03_\n\n- Open-source `AvoidUsingNotNullOperator`, `InflationInItemDecoration`, and `DoNotCallViewToString` checks.\n\n0.8.0\n-----\n\n_2024-10-02_\n\n- **Enhancement**: Tweak explanation for default dispatcher use in rx<->flow interop.\n- **Enhancement**: Switch to stable kotlin-metadata artifact\n- **Fix**: Allow Dagger providers to be called from test sources.\n- Build against lint `8.8.0-alpha04`.\n- Update `api`/`minApi` to `16` (Lint 8.7.0+).\n- Build against Kotlin `2.0.20`.\n- Target Kotlin language version `1.9` in lint-checks (imposed by lint), `2.0` in lint-annotations.\n\n0.7.3\n-----\n\n_2024-05-03_\n\n- Fix `DoNotMockAnything` to use `setEnabledByDefault(false)`.\n\n0.7.2\n-----\n\n_2024-05-02_\n\n- Add new `DoNotMockAnything` check. This is disabled by default. This marks _any_ mock as a lint error. This is useful for enforcing a no-mocks policy in your codebase.\n- Update lint to `31.5.0-alpha07`.\n- Update to kotlin `1.9.23`.\n- [docs] Expand Mock option explanation for use with multiple issues.\n\nSpecial thanks to [@utwyko](https://github.com/utwyko) for contributing to this release!\n\n0.7.1\n-----\n\n_2024-03-27_\n\n- Add `MustUseNamedParamsDetector` to lint registry.\n- Update lint to `31.5.0-alpha02`.\n- Target Kotlin API/language version `1.9`.\n\n0.7.0\n-----\n\n_2023-10-27_\n\n- Lower lint API back to `31.3.0-alpha05` as newer versions targeted kotlin 1.9.20 betas without us realizing it.\n- Improve explanation for sealed class mock detector to mention that Mockito can't mock them at all in Java 17+.\n- Promote `PlatformTypeMockDetector` to error severity.\n- Make `DenyListedApi` entries more configurable. Initial change is that blocking APIs are now reported with the ID `DenyListedBlockingApi`.\n- Support multiple mock report modes for the `mock-report` option. Modes are `NONE`, `ERRORS`, and `ALL`. Default is `NONE`. Now the report file is `build/reports/mockdetector/mock-report.csv` and the second column is the severity. This allows reporting all mocks for extra analysis.\n\n0.6.1\n-----\n\n_2023-10-09_\n\n- **Enhancement**: Add `mock-report` option to `MockDetector`s to generate a report of all mocked types in a project.\n- Update to lint `31.3.0-alpha07`.\n\n0.6.0\n-----\n\n_2023-09-28_\n\n- **New**: Add `ExceptionMessage` check that ensures that calls to `check`, `checkNotNull`, `require`, and `requireNotNull` functions always include a message.\n- **Enhancement**: Add support for custom mock factories and mock annotations to `MockDetector`.\n  - `mock-annotations` is a comma-separated list of mock annotations' fully-qualified names. Default is `org.mockito.Mock,org.mockito.Spy`.\n  - `mock-factories` is a comma-separated list of mock factories (i.e. `org.mockito.Mockito#methodName`). Default is `org.mockito.Mockito#mock,org.mockito.Mockito#spy,slack.test.mockito.MockitoHelpers#mock,slack.test.mockito.MockitoHelpersKt#mock`.\n- Update lint to `31.3.0-alpha05`.\n\nSpecial thanks to [@SimonMarquis](https://github.com/SimonMarquis) for contributing to this release!\n\n0.5.1\n-----\n\n_2023-09-09_\n\n- **Fix**: Allow `@Provides` in companion objects of `@Module` classes.\n\n0.5.0\n-----\n\n_2023-09-08_\n\n- **New**: Add a bunch more checks around correct usage of Dagger `@Binds` and `@Provides` methods.\n- **Fix**: Remove `BindsCanBeExtensionFunction` lint as this is prohibited now in Dagger 2.48+.\n- Update to lint `31.3.0-alpha03`.\n- Update to Kotlin `1.9.10`.\n\n0.4.0\n-----\n\n_2023-07-20_\n\n- **New**: Denylist blocking RxJava 3 operators and coroutines' `runBlocking` in favor of `TestObserver` and `runTest`/Turbine.\n- **New**: Denylist coroutines' `runCatching`.\n- **New**: Denylist `java.util.Date`, `java.text.DateFormat`, and `java.text.SimpleDateFormat` in favor of `java.time.*`/`kotlin.time.*`/etc APIs.\n- **Enhancement**: Specifically report denylisted function name only in lint report, not the whole call expression.\n- **Enhancement**: Update kotlinx-metadata to `0.7.0` to better support Kotlin 2.0.\n- Update to lint `31.2.0-alpha13` (lint API `14`).\n\n0.3.0\n-----\n\n_2023-05-31_\n\n- **New**: Use kotlinx-metadata to parse `Metadata` annotations of `PsiCompiledElement` types to better handle Kotlin language features. Currently used in mock checks and Moshi checks. Please star this issue: https://issuetracker.google.com/issues/283654244.\n- **New**: Add DoNotMock check for `object` types.\n- **New**: Add DoNotMock check for `sealed` types. Subtypes should be used instead.\n- **New**: Add DoNotMock check for `record` types. Same motivation as data classes.\n- **New**: Add DoNotMock check for platform types (e.g. `java.*`, `kotlin.*`, `android.*`, their `*x.*` variants). Prefer real implementations or fakes instead.\n  - This is a big change so this one is just a warning for now.\n- **Enhancement**: `MockDetector` revamp. All mock checks now run within the same detector to better utilize metadata catching.\n- **Enhancement**: Improve mock check location reporting.\n- **Enhancement**: Improve mock check messages to specify the erroring type.\n- **Enhancement**: Add `reason` properties to `@KotlinOnly`/`@JavaOnly` annotations.\n- **Enhancement**: Add more information to the `Vendor` details.\n- Raise min lint API to `14`.\n- Update kotlin to `1.8.21`. Updated language version to this too to match lint.\n- Update lint to `31.2.0-alpha06`.\n\n0.2.3\n-----\n\n_2023-02-22_\n\n- **New**: `ParcelizeFunctionProperty` check that errors when a `@Parcelize` class has a function property.\n\n0.2.2\n-----\n\n_2023-02-09_\n\n- **Removed**: Compose lints have been removed and published in a separate project: https://github.com/slackhq/compose-lints\n\n0.2.1\n-----\n\n_2023-01-26_\n\n- **Fix**: Improve and fix a number of explanation string formatting in the new compose lints.\n\n0.2.0\n-----\n\n_2023-01-25_\n\n- **New**: Ported most of the Twitter [compose-rules](https://github.com/twitter/compose-rules) checks to lint. We're packaging them in this project right now, but will likely publish them from a separate repo in the future.\n- Target lint-api `31.1.0-alpha01`.\n- Update to Kotlin API version `1.7`. Lint `8.1.0-alpha01` or later is now required.\n- Modernize various build infra (Kotlin `1.8.0`, JDK 19, Gradle 7.6).\n\n0.1.1\n-----\n\n_2022-11-30_\n\n- **Fix**: Fallback to file package name in `MissingResourceImportAliasDetector` if project package name is null.\n\n0.1.0\n-----\n\n_2022-11-17_\n\n* Initial release on maven central.\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "# Comment line immediately above ownership line is reserved for related other information. Please be careful while editing.\n#ECCN:Open Source\n#GUSINFO:Open Source,Open Source Workflow\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        https://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       https://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "slack-lints\n===========\n\nThis repository contains a collection of custom Android/Kotlin lint checks we use in our Android and Kotlin code bases at Slack.\n\nWhile we do publish artifacts to Maven Central, some of the lints checks may only really be relevant to Slack's codebase. We [develop\nthese in the open](https://slack.engineering/developing-in-the-open/) to knowledge-share with the community.\n\n## Installation\n\nAdd the dependency to the `lintChecks` configuration. Note for non-android projects, you must apply the `com.android.lint` Gradle plugin to use this.\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.slack.lint/slack-lint-checks.svg)](https://mvnrepository.com/artifact/com.slack.lint/slack-lint-checks)\n\n```kotlin\ndependencies {\n  lintChecks(\"com.slack.lint:slack-lint-checks:<version>\")\n}\n```\n\n## Overview\n\n### Do Not Mock\n\nThe `slack.lint.mocking` package contains several detectors and utilities for detecting mocking\nof types that should not be mocked. This is similar to ErrorProne's `DoNotMockChecker` and acts as\nan enforcement layer to APIs and classes annotated with `@DoNotMock`. This also detects common types that should never be mocked, such as Kotlin `data` classes or AutoValue classes.\n\n### Inclusivity\n\nIn order to write more inclusive code, we have an `InclusiveNamingChecker` tool to check for a\nconfigurable list of non-inclusive names.\n\n### Moshi\n\n`MoshiUsageDetector` contains a wealth of checks for common programmer errors when writing classes\nfor use with [Moshi](https://github.com/square/moshi) and [MoshiX](https://github.com/ZacSweers/MoshiX).\n\n### Misc\n\n* `JavaOnlyDetector` - detects use of Java-only APIs from Kotlin. Based on the original unreleased implementation in [uber/lint-checks](https://github.com/uber/lint-checks).\n* `DaggerKotlinIssuesDetector` - detects some known issues when using [Dagger](https://github.com/google/dagger) in Kotlin code.\n* `RetrofitUsageDetector` - detects some common issues when using [Retrofit](https://github.com/square/retrofit).\n* `DenyListedApi` – detects use of APIs that just shouldn't be used.\n* `MustUseNamedParams` – can be used on functions that should _always_ use named parameters. Useful for APIs that have a lot of parameters and/or may change their order and you want to keep changes source-compatible.\n* ...and a plethora of others!\n\nLicense\n--------\n\n    Copyright 2021 Slack Technologies, LLC\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n"
  },
  {
    "path": "RELEASING.md",
    "content": "Releasing\n=========\n\n1. Update the `CHANGELOG.md` for the impending release.\n2. Run `./release.sh <version>`.\n3. Publish the release on the repo's releases tab.\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\nimport com.diffplug.gradle.spotless.KotlinExtension\nimport com.diffplug.gradle.spotless.SpotlessExtension\nimport com.vanniktech.maven.publish.MavenPublishBaseExtension\nimport io.gitlab.arturbosch.detekt.Detekt\nimport org.jetbrains.dokka.gradle.DokkaTaskPartial\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\nimport org.jetbrains.kotlin.gradle.dsl.KotlinVersion\nimport org.jetbrains.kotlin.gradle.tasks.KotlinCompile\n\nplugins {\n  alias(libs.plugins.kotlin.jvm) apply false\n  alias(libs.plugins.spotless) apply false\n  alias(libs.plugins.mavenPublish) apply false\n  alias(libs.plugins.dokka)\n  alias(libs.plugins.detekt)\n  alias(libs.plugins.lint) apply false\n  alias(libs.plugins.ksp) apply false\n}\n\ndokka {\n  dokkaPublications.html {\n    outputDirectory.set(rootDir.resolve(\"docs/api/0.x\"))\n    includes.from(project.layout.projectDirectory.file(\"README.md\"))\n  }\n}\n\nval ktfmtVersion = libs.versions.ktfmt.get()\n\nallprojects {\n  apply(plugin = \"com.diffplug.spotless\")\n  configure<SpotlessExtension> {\n    format(\"misc\") {\n      target(\"*.md\", \".gitignore\")\n      trimTrailingWhitespace()\n      endWithNewline()\n    }\n\n    val externalSourceGlobs =\n      arrayOf(\"**/denylistedapis/*.kt\", \"**/ExceptionMessageDetector*.kt\", \"**/util/Names.kt\")\n\n    kotlin {\n      target(\"**/*.kt\")\n      ktfmt(ktfmtVersion).googleStyle()\n      trimTrailingWhitespace()\n      endWithNewline()\n      licenseHeaderFile(rootProject.file(\"spotless/spotless.kt\"))\n      targetExclude(\"**/spotless.kt\", *externalSourceGlobs)\n    }\n    // Externally adapted sources that should preserve their license header\n    format(\"kotlinExternal\", KotlinExtension::class.java) {\n      target(*externalSourceGlobs)\n      ktfmt(ktfmtVersion).googleStyle()\n      trimTrailingWhitespace()\n      endWithNewline()\n    }\n    kotlinGradle {\n      ktfmt(ktfmtVersion).googleStyle()\n      trimTrailingWhitespace()\n      endWithNewline()\n      licenseHeaderFile(\n        rootProject.file(\"spotless/spotless.kt\"),\n        \"(import|plugins|buildscript|dependencies|pluginManagement)\",\n      )\n    }\n  }\n}\n\nval jdk = libs.versions.jdk.get().toInt()\nval lintJvmTargetString: String = libs.versions.lintJvmTarget.get()\nval runtimeJvmTargetString: String = libs.versions.runtimeJvmTarget.get()\n\nsubprojects {\n  val isChecksProject = path == \":slack-lint-checks\"\n  val jvmTargetString =\n    if (isChecksProject) {\n      lintJvmTargetString\n    } else {\n      runtimeJvmTargetString\n    }\n  val jvmTargetInt = jvmTargetString.toInt()\n  pluginManager.withPlugin(\"java\") {\n    configure<JavaPluginExtension> {\n      toolchain { languageVersion.set(JavaLanguageVersion.of(jdk)) }\n    }\n\n    tasks.withType<JavaCompile>().configureEach { options.release.set(jvmTargetInt) }\n  }\n\n  pluginManager.withPlugin(\"org.jetbrains.kotlin.jvm\") {\n    tasks.withType<KotlinCompile>().configureEach {\n      compilerOptions {\n        jvmTarget.set(JvmTarget.fromTarget(jvmTargetString))\n        // TODO re-enable on checks if lint ever targets latest kotlin versions\n        if (isChecksProject) {\n          // Lint forces Kotlin 1.9 still\n          languageVersion.set(KotlinVersion.KOTLIN_1_9)\n        } else {\n          allWarningsAsErrors.set(true)\n        }\n      }\n    }\n  }\n\n  tasks.withType<Detekt>().configureEach { jvmTarget = jvmTargetString }\n\n  pluginManager.withPlugin(\"com.vanniktech.maven.publish\") {\n    apply(plugin = \"org.jetbrains.dokka\")\n\n    tasks.withType<DokkaTaskPartial>().configureEach {\n      outputDirectory.set(layout.buildDirectory.dir(\"docs/partial\"))\n      dokkaSourceSets.configureEach { skipDeprecated.set(true) }\n    }\n\n    configure<MavenPublishBaseExtension> {\n      publishToMavenCentral(automaticRelease = true)\n      signAllPublications()\n    }\n  }\n}\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nkotlin = \"2.3.21\"\nktfmt = \"0.58\"\njdk = \"23\"\n# lint checks must target JDK 17, but the runtime should remain 11\nlintJvmTarget = \"17\"\nruntimeJvmTarget = \"11\"\nlint = \"31.12.0-alpha09\"\n\n[plugins]\nbuildConfig = { id = \"com.github.gmazzo.buildconfig\", version = \"6.0.9\" }\ndetekt = { id = \"io.gitlab.arturbosch.detekt\", version = \"1.23.8\" }\ndokka = { id = \"org.jetbrains.dokka\", version = \"2.2.0\" }\nlint = { id = \"com.android.lint\", version = \"9.2.0\" }\nkotlin-jvm = { id = \"org.jetbrains.kotlin.jvm\", version.ref = \"kotlin\" }\nksp = { id = \"com.google.devtools.ksp\", version = \"2.3.7\" }\nmavenPublish = { id = \"com.vanniktech.maven.publish\", version = \"0.36.0\" }\nshadow = { id = \"com.gradleup.shadow\", version = \"9.4.1\" }\nspotless = { id = \"com.diffplug.spotless\", version = \"8.4.0\" }\n\n[libraries]\nautoService-annotations = \"com.google.auto.service:auto-service-annotations:1.1.1\"\nautoService-ksp = \"dev.zacsweers.autoservice:auto-service-ksp:1.2.0\"\njunit = \"junit:junit:4.13.2\"\nkotlin-metadata = { module = \"org.jetbrains.kotlin:kotlin-metadata-jvm\", version.ref = \"kotlin\" }\nktfmt = { module = \"com.facebook:ktfmt\", version.ref = \"ktfmt\" }\neithernet = \"com.slack.eithernet:eithernet:2.0.0\"\nretrofit = \"com.squareup.retrofit2:retrofit:3.0.0\"\nlint-api = { module = \"com.android.tools.lint:lint-api\", version.ref = \"lint\" }\nlint-checks = { module = \"com.android.tools.lint:lint-checks\", version.ref = \"lint\" }\nlint = { module = \"com.android.tools.lint:lint\", version.ref = \"lint\" }\nlint-tests = { module = \"com.android.tools.lint:lint-tests\", version.ref = \"lint\" }\nlint-testUtils = { module = \"com.android.tools:testutils\", version.ref = \"lint\" }\n\n[bundles]\nlintApi = [\"lint-api\", \"lint-checks\"]\nlintTest = [\"lint\", \"lint-tests\", \"lint-testUtils\"]\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.4.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.jvmargs=-Xms1g -Xmx4g -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=1g\n\norg.gradle.parallel=true\norg.gradle.configureondemand=true\norg.gradle.caching=true\norg.gradle.configuration-cache=true\n\nandroid.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,\\\n  android.experimental.lint.missingBaselineIsEmptyBaseline,\\\n  android.lint.useK2Uast\n\nandroid.experimental.lint.missingBaselineIsEmptyBaseline=true\nandroid.lint.useK2Uast=true\n# Always use/test K2 now\nsystemProp.lint.use.fir.uast=true\n\nksp.useKSP2=true\n\norg.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled\norg.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true\n\n# Versioning bits\nGROUP=com.slack.lint\nPOM_URL=https://github.com/slackhq/slack-lints/\nPOM_SCM_URL=https://github.com/slackhq/slack-lints/\nPOM_SCM_CONNECTION=scm:git:git://github.com/slackhq/slack-lints.git\nPOM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/slackhq/slack-lints.git\nPOM_LICENCE_DIST=repo\nPOM_DEVELOPER_ID=slackhq\nPOM_DEVELOPER_NAME=Slack Technologies, LLC.\nPOM_DEVELOPER_URL=https://github.com/slackhq\nPOM_INCEPTION_YEAR=2021\nVERSION_NAME=1.0.0-SNAPSHOT\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "release.sh",
    "content": "#!/usr/bin/env bash\n\nset -exo pipefail\n\n# Gets a property out of a .properties file\n# usage: getProperty $key $filename\nfunction getProperty() {\n    grep \"${1}\" \"$2\" | cut -d'=' -f2\n}\n\nNEW_VERSION=$1\nSNAPSHOT_VERSION=$(getProperty 'VERSION_NAME' gradle.properties)\n\necho \"Publishing $NEW_VERSION\"\n\n# Prepare release\nsed -i '' \"s/${SNAPSHOT_VERSION}/${NEW_VERSION}/g\" gradle.properties\ngit commit -am \"Prepare for release $NEW_VERSION.\"\ngit tag -a \"$NEW_VERSION\" -m \"Version $NEW_VERSION\"\n\n# Publish\n./gradlew publish\n\n# Prepare next snapshot\necho \"Restoring snapshot version $SNAPSHOT_VERSION\"\nsed -i '' \"s/${NEW_VERSION}/${SNAPSHOT_VERSION}/g\" gradle.properties\ngit commit -am \"Prepare next development version.\"\n\n# Push it all up\ngit push && git push --tags"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:base\"\n  ],\n  \"branchPrefix\": \"renovate/\",\n  \"gitAuthor\": \"OSS-Bot <svc-oss-bot@slack-corp.com>\",\n  \"prHourlyLimit\": 10,\n  \"repositories\": [\n    \"slackhq/slack-lints\"\n  ],\n  \"packageRules\": [\n    {\n      \"matchPackageNames\": [\n        \"renovatebot/github-action\"\n      ],\n      \"extends\": [\n        \"schedule:monthly\"\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "settings.gradle.kts",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\nimport java.util.Locale\n\npluginManagement {\n  repositories {\n    mavenCentral()\n    google()\n    // Last because this proxies jcenter!\n    gradlePluginPortal()\n  }\n}\n\ndependencyResolutionManagement {\n  versionCatalogs {\n    if (System.getenv(\"DEP_OVERRIDES\") == \"true\") {\n      val overrides = System.getenv().filterKeys { it.startsWith(\"DEP_OVERRIDE_\") }\n      maybeCreate(\"libs\").apply {\n        for ((key, value) in overrides) {\n          val catalogKey = key.removePrefix(\"DEP_OVERRIDE_\").lowercase(Locale.getDefault())\n          println(\"Overriding $catalogKey with $value\")\n          version(catalogKey, value)\n        }\n      }\n    }\n  }\n  repositories {\n    google()\n    mavenCentral()\n  }\n}\n\nrootProject.name = \"slack-lints\"\n\ninclude(\":slack-lint-checks\", \":slack-lint-annotations\")\n"
  },
  {
    "path": "slack-lint-annotations/build.gradle.kts",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\nplugins {\n  alias(libs.plugins.kotlin.jvm)\n  alias(libs.plugins.mavenPublish)\n}\n"
  },
  {
    "path": "slack-lint-annotations/gradle.properties",
    "content": "POM_ARTIFACT_ID=slack-lint-annotations\nPOM_NAME=Slack Lint Annotations\nPOM_DESCRIPTION=Slack lint annotations."
  },
  {
    "path": "slack-lint-annotations/src/main/kotlin/slack/lint/annotations/AllowUnitResult.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.annotations\n\n/**\n * When applied to a suspend Retrofit function, this annotation permits the function to have a\n * return type of [Unit], which otherwise will be flagged as an issue.\n */\n@Target(AnnotationTarget.FUNCTION)\n@Retention(AnnotationRetention.SOURCE)\nannotation class AllowUnitResult\n"
  },
  {
    "path": "slack-lint-annotations/src/main/kotlin/slack/lint/annotations/DoNotMock.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.annotations\n\nimport java.lang.annotation.Inherited\nimport kotlin.annotation.AnnotationRetention.RUNTIME\nimport kotlin.annotation.AnnotationTarget.CLASS\n\n/**\n * Annotation representing a type that should not be mocked.\n *\n * When marking a type `@DoNotMock`, you should always point to alternative testing solutions such\n * as standard fakes or other testing utilities.\n *\n * Mockito tests can enforce this annotation by using a custom MockMaker which intercepts creation\n * of mocks.\n */\n@Inherited\n@Target(CLASS)\n@Retention(RUNTIME)\nannotation class DoNotMock(\n  /**\n   * The reason why the annotated type should not be mocked.\n   *\n   * This should suggest alternative APIs to use for testing objects of this type.\n   */\n  val value: String = \"Create a real instance instead\"\n)\n"
  },
  {
    "path": "slack-lint-annotations/src/main/kotlin/slack/lint/annotations/JavaOnly.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.annotations\n\n/**\n * An annotation that is used to denote methods that should not be called from any context other\n * than Java code. This is important for cases in APIs that support both Kotlin and Java in\n * different ways.\n *\n * @property reason An optional reason why this API is intended for Java only.\n */\nannotation class JavaOnly(val reason: String = \"\")\n"
  },
  {
    "path": "slack-lint-annotations/src/main/kotlin/slack/lint/annotations/KotlinOnly.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.annotations\n\n/**\n * An annotation that is used to denote methods that should not be called from any context other\n * than Kotlin code. This is important for cases in APIs that support both Kotlin and Java in\n * different ways.\n *\n * @property reason An optional reason why this API is intended for Java only.\n */\nannotation class KotlinOnly(val reason: String = \"\")\n"
  },
  {
    "path": "slack-lint-annotations/src/main/kotlin/slack/lint/annotations/MustUseNamedParams.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.annotations\n\n/**\n * Callers to this function must named all parameters. This is useful in cases where arguments may\n * change in order and you want to avoid source-breaking changes.\n */\n@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)\n@Retention(AnnotationRetention.RUNTIME)\nannotation class MustUseNamedParams\n"
  },
  {
    "path": "slack-lint-annotations/src/main/kotlin/slack/lint/annotations/RestrictCallsTo.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.annotations\n\nimport java.lang.annotation.Inherited\nimport kotlin.annotation.AnnotationRetention.RUNTIME\nimport kotlin.annotation.AnnotationTarget.FUNCTION\n\n/**\n * Annotation representing a function or property that should not be called outside of a given\n * [scope]. Similar to androidx's `RestrictTo` annotation but just for calls.\n */\n@Inherited\n@Target(FUNCTION)\n@Retention(RUNTIME)\nannotation class RestrictCallsTo(\n  /** The target scope. Only file is supported for now, toe-hold left for possible future scopes. */\n  val scope: Int = FILE\n) {\n  companion object {\n    const val FILE = 0\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/build.gradle.kts",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\nimport com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer\nimport org.jetbrains.kotlin.gradle.dsl.KotlinVersion\nimport org.jetbrains.kotlin.gradle.tasks.KotlinCompile\n\nplugins {\n  alias(libs.plugins.kotlin.jvm)\n  // Run lint on the lints! https://groups.google.com/g/lint-dev/c/q_TVEe85dgc\n  alias(libs.plugins.lint)\n  alias(libs.plugins.ksp)\n  alias(libs.plugins.mavenPublish)\n  alias(libs.plugins.shadow)\n  alias(libs.plugins.buildConfig)\n}\n\nval lintKotlinVersion = KotlinVersion(2, 1, 21)\n\nbuildConfig {\n  packageName(\"slack.lint\")\n  useKotlinOutput { internalVisibility = true }\n  sourceSets.getByName(\"test\") {\n    buildConfigField(\n      \"kotlin.KotlinVersion\",\n      \"LINT_KOTLIN_VERSION\",\n      \"KotlinVersion(${lintKotlinVersion.major}, ${lintKotlinVersion.minor}, ${lintKotlinVersion.patch})\",\n    )\n  }\n}\n\nlint {\n  htmlReport = true\n  xmlReport = true\n  textReport = true\n  absolutePaths = false\n  checkTestSources = true\n  baseline = file(\"lint-baseline.xml\")\n  disable += setOf(\"GradleDependency\")\n  fatal += setOf(\"LintDocExample\", \"LintImplPsiEquals\", \"UastImplementation\")\n}\n\nval shade: Configuration = configurations.maybeCreate(\"compileShaded\")\n\nconfigurations.getByName(\"compileOnly\").extendsFrom(shade)\n\ntasks.test {\n  // Disable noisy java applications launching during tests\n  jvmArgs(\"-Djava.awt.headless=true\")\n  maxParallelForks = Runtime.getRuntime().availableProcessors() * 2\n}\n\ndependencies {\n  compileOnly(libs.bundles.lintApi)\n  ksp(libs.autoService.ksp)\n  implementation(libs.autoService.annotations)\n  shade(libs.kotlin.metadata) { exclude(group = \"org.jetbrains.kotlin\", module = \"kotlin-stdlib\") }\n\n  // Dupe the dep because the shaded version is compileOnly in the eyes of the gradle configurations\n  testImplementation(libs.kotlin.metadata) {\n    exclude(group = \"org.jetbrains.kotlin\", module = \"kotlin-stdlib\")\n  }\n  testImplementation(libs.bundles.lintTest)\n  testImplementation(libs.junit)\n\n  // For IDE linking of APIs\n  testImplementation(libs.retrofit)\n  testImplementation(libs.eithernet) { exclude(group = \"org.jetbrains.kotlin\") }\n}\n\nval kgpKotlinVersion =\n  KotlinVersion.fromVersion(lintKotlinVersion.toString().substringBeforeLast('.'))\n\ntasks.withType<KotlinCompile>().configureEach {\n  compilerOptions {\n    // Lint forces Kotlin (regardless of what version the project uses), so this\n    // forces a matching language level for now. Similar to `targetCompatibility` for Java.\n    // This should match the value in LintKotlinVersionCheckTest.kt\n    apiVersion.set(kgpKotlinVersion)\n    languageVersion.set(kgpKotlinVersion)\n  }\n}\n\nval shadowJar =\n  tasks.shadowJar.apply {\n    configure {\n      archiveClassifier.set(\"\")\n      configurations = listOf(shade)\n      relocate(\"kotlinx.metadata\", \"slack.lint.shaded.kotlinx.metadata\")\n      transformers.add(ServiceFileTransformer())\n    }\n  }\n\nartifacts {\n  runtimeOnly(shadowJar)\n  archives(shadowJar)\n}\n"
  },
  {
    "path": "slack-lint-checks/gradle.properties",
    "content": "POM_ARTIFACT_ID=slack-lint-checks\nPOM_NAME=Slack Lint Checks\nPOM_DESCRIPTION=Slack lint checks.\n\n# Opt-out flag for bundling Kotlin standard library because Lint forces its own version\n# See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library\nkotlin.stdlib.default.dependency=false\n"
  },
  {
    "path": "slack-lint-checks/lint-baseline.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<issues format=\"6\" by=\"lint 8.11.0\" type=\"baseline\" client=\"gradle\" dependencies=\"false\" name=\"AGP (8.11.0)\" variant=\"all\" version=\"8.11.0\">\n\n    <issue\n        id=\"LintDocExample\"\n        message=\"Expected to also find a documentation example test (`testDocumentationExample`) which shows a simple, typical scenario which triggers the test, and which will be extracted into lint&apos;s per-issue documentation pages\"\n        errorLine1=\"  @Test\"\n        errorLine2=\"  ^\">\n        <location\n            file=\"src/test/java/slack/lint/DeprecatedSqlUsageDetectorTest.kt\"\n            line=\"16\"\n            column=\"3\"/>\n    </issue>\n\n    <issue\n        id=\"LintDocExample\"\n        message=\"Expected to also find a documentation example test (`testDocumentationExample`) which shows a simple, typical scenario which triggers the test, and which will be extracted into lint&apos;s per-issue documentation pages\"\n        errorLine1=\"  @Test\"\n        errorLine2=\"  ^\">\n        <location\n            file=\"src/test/java/slack/lint/resources/FullyQualifiedResourceDetectorTest.kt\"\n            line=\"25\"\n            column=\"3\"/>\n    </issue>\n\n    <issue\n        id=\"LintDocExample\"\n        message=\"Expected to also find a documentation example test (`testDocumentationExample`) which shows a simple, typical scenario which triggers the test, and which will be extracted into lint&apos;s per-issue documentation pages\"\n        errorLine1=\"  @Test\"\n        errorLine2=\"  ^\">\n        <location\n            file=\"src/test/java/slack/lint/resources/MissingResourceImportAliasDetectorTest.kt\"\n            line=\"24\"\n            column=\"3\"/>\n    </issue>\n\n    <issue\n        id=\"LintDocExample\"\n        message=\"Expected to also find a documentation example test (`testDocumentationExample`) which shows a simple, typical scenario which triggers the test, and which will be extracted into lint&apos;s per-issue documentation pages\"\n        errorLine1=\"  @Test\"\n        errorLine2=\"  ^\">\n        <location\n            file=\"src/test/java/slack/lint/mocking/MockDetectorOptionsTest.kt\"\n            line=\"27\"\n            column=\"3\"/>\n    </issue>\n\n    <issue\n        id=\"LintDocExample\"\n        message=\"Expected to also find a documentation example test (`testDocumentationExample`) which shows a simple, typical scenario which triggers the test, and which will be extracted into lint&apos;s per-issue documentation pages\"\n        errorLine1=\"  private fun testFiles() =\"\n        errorLine2=\"  ^\">\n        <location\n            file=\"src/test/java/slack/lint/MoshiUsageDetectorTest.kt\"\n            line=\"2077\"\n            column=\"3\"/>\n    </issue>\n\n    <issue\n        id=\"LintDocExample\"\n        message=\"Expected to also find a documentation example test (`testDocumentationExample`) which shows a simple, typical scenario which triggers the test, and which will be extracted into lint&apos;s per-issue documentation pages\"\n        errorLine1=\"  private fun testViolatingExpressionLeft(markPoint: String) {\"\n        errorLine2=\"  ^\">\n        <location\n            file=\"src/test/java/slack/lint/text/SpanMarkPointMissingMaskDetectorTest.kt\"\n            line=\"58\"\n            column=\"3\"/>\n    </issue>\n\n    <issue\n        id=\"LintDocExample\"\n        message=\"Expected to also find a documentation example test (`testDocumentationExample`) which shows a simple, typical scenario which triggers the test, and which will be extracted into lint&apos;s per-issue documentation pages\"\n        errorLine1=\"  @Test\"\n        errorLine2=\"  ^\">\n        <location\n            file=\"src/test/java/slack/lint/ViewContextDetectorTest.kt\"\n            line=\"16\"\n            column=\"3\"/>\n    </issue>\n\n    <issue\n        id=\"LintDocExample\"\n        message=\"Expected to also find a documentation example test (`testDocumentationExample`) which shows a simple, typical scenario which triggers the test, and which will be extracted into lint&apos;s per-issue documentation pages\"\n        errorLine1=\"  @Test\"\n        errorLine2=\"  ^\">\n        <location\n            file=\"src/test/java/slack/lint/resources/WrongResourceImportAliasDetectorTest.kt\"\n            line=\"24\"\n            column=\"3\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"        &quot;Count value in formatted string resource.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"31\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        BRIEF_DESCRIPTION_PREFIX_DEFAULT + BRIEF_DESCRIPTION_SUFFIX,\"\n        errorLine2=\"        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/DeprecatedAnnotationDetector.kt\"\n            line=\"64\"\n            column=\"9\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Dagger provider methods should not be called directly by user code.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/DoNotCallProvidersDetector.kt\"\n            line=\"27\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;EitherNet&quot; looks like a code reference; surround with backtics in string to display as symbol, e.g. \\`EitherNet\\`\"\n        errorLine1=\"            message = &quot;Repository APIs should not expose EitherNet types directly.&quot;,\"\n        errorLine2=\"                                                         ~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/eithernet/DoNotExposeEitherNetInRepositoriesDetector.kt\"\n            line=\"86\"\n            column=\"58\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"Single sentence error messages should not end with a period\"\n        errorLine1=\"            message = &quot;Repository APIs should not expose EitherNet types directly.&quot;,\"\n        errorLine2=\"                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/eithernet/DoNotExposeEitherNetInRepositoriesDetector.kt\"\n            line=\"86\"\n            column=\"24\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"        briefDescription = &quot;Repository APIs should not expose EitherNet types directly.&quot;,\"\n        errorLine2=\"                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/eithernet/DoNotExposeEitherNetInRepositoriesDetector.kt\"\n            line=\"108\"\n            column=\"29\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;EitherNet&quot; looks like a code reference; surround with backtics in string to display as symbol, e.g. \\`EitherNet\\`\"\n        errorLine1=\"          &quot;EitherNet (and networking in general) should be an implementation detail of the repository layer.&quot;,\"\n        errorLine2=\"           ~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/eithernet/DoNotExposeEitherNetInRepositoriesDetector.kt\"\n            line=\"110\"\n            column=\"12\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"        &quot;Use Slack&apos;s internal `@DoNotMock` annotation.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/mocking/ErrorProneDoNotMockDetector.kt\"\n            line=\"25\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"          &quot;Fragment dependencies should be injected using constructor injections only.&quot;,\"\n        errorLine2=\"           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/FragmentDaggerFieldInjectionDetector.kt\"\n            line=\"68\"\n            column=\"12\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"          &quot;Fragment dependencies should be injected using constructor injections only.&quot;,\"\n        errorLine2=\"           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/FragmentDaggerFieldInjectionDetector.kt\"\n            line=\"68\"\n            column=\"12\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"          &quot;Fragment dependencies should be injected using the Fragment&apos;s constructor.&quot;,\"\n        errorLine2=\"           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/FragmentDaggerFieldInjectionDetector.kt\"\n            line=\"85\"\n            column=\"12\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"          &quot;Fragment dependencies should be injected using the Fragment&apos;s constructor.&quot;,\"\n        errorLine2=\"           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/FragmentDaggerFieldInjectionDetector.kt\"\n            line=\"85\"\n            column=\"12\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"          &quot;Resources should use an import alias instead of being fully qualified.&quot;,\"\n        errorLine2=\"           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/resources/FullyQualifiedResourceDetector.kt\"\n            line=\"152\"\n            column=\"12\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"Multi-line issue explanation strings will interpret line separators as hard breaks, and this looks like a continuation of the same paragraph. Consider using \\ at the end of the previous line to indicate that the lines should be joined, or add a blank line between unrelated sentences, or suppress this issue type here.\"\n        errorLine1=\"            &quot;import slack.l10n.R as L10nR\\n&quot; +\"\n        errorLine2=\"             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/resources/FullyQualifiedResourceDetector.kt\"\n            line=\"154\"\n            column=\"14\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;...getString(L10nR.string.app_name)&quot; looks like a call; surround with backtics in string to display as symbol, e.g. \\`...getString(L10nR.string.app_name)\\`\"\n        errorLine1=\"            &quot;...getString(L10nR.string.app_name)&quot;,\"\n        errorLine2=\"             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/resources/FullyQualifiedResourceDetector.kt\"\n            line=\"156\"\n            column=\"14\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Use Slack&apos;s JavaPreconditions instead of Guava&apos;s Preconditions checks&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/GuavaPreconditionsDetector.kt\"\n            line=\"127\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Use Slack&apos;s JavaPreconditions instead of Guava&apos;s Preconditions checks&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/GuavaPreconditionsDetector.kt\"\n            line=\"127\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Kotlin precondition checks should use the Kotlin standard library checks&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/GuavaPreconditionsDetector.kt\"\n            line=\"140\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Kotlin precondition checks should use the Kotlin standard library checks&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/GuavaPreconditionsDetector.kt\"\n            line=\"140\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Only Kotlin classes should be injected in order for Anvil to work.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/InjectInJavaDetector.kt\"\n            line=\"65\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"        MESSAGE_LINT_ERROR_TITLE,\"\n        errorLine2=\"        ~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/JavaOnlyDetector.kt\"\n            line=\"53\"\n            column=\"9\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Using JsonInflater.inflate/deflate with a Moshi-incompatible type.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/JsonInflaterMoshiCompatibilityDetector.kt\"\n            line=\"173\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;JsonInflater&quot; looks like a code reference; surround with backtics in string to display as symbol, e.g. \\`JsonInflater\\`\"\n        errorLine1=\"          Classes used with JsonInflater.inflate/deflate must be annotated with @JsonClass or @AdaptedBy to make it \\\"\n        errorLine2=\"                            ~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/JsonInflaterMoshiCompatibilityDetector.kt\"\n            line=\"175\"\n            column=\"29\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"        &quot;Use slack.foundation.coroutines.android.MainScope.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MainScopeUsageDetector.kt\"\n            line=\"38\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"          &quot;Missing import alias for R class.&quot;,\"\n        errorLine2=\"           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/resources/MissingResourceImportAliasDetector.kt\"\n            line=\"145\"\n            column=\"12\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"Single sentence error messages should not end with a period\"\n        errorLine1=\"        &quot;Name &apos;$jsonName&apos; is duplicated by member &apos;${existingMember.name}&apos;.&quot;,\"\n        errorLine2=\"        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"855\"\n            column=\"9\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"Single sentence error messages should not end with a period\"\n        errorLine1=\"        &quot;Name &apos;$jsonName&apos; is duplicated by member &apos;${member.name}&apos;.&quot;,\"\n        errorLine2=\"        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"860\"\n            column=\"9\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Calls to @MustUseNamedParams-annotated methods must name all parameters.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MustUseNamedParamsDetector.kt\"\n            line=\"58\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;MustUseNamedParams&quot; looks like a code reference; surround with backtics in string to display as symbol, e.g. \\`MustUseNamedParams\\`\"\n        errorLine1=\"        &quot;Calls to @MustUseNamedParams-annotated methods must name all parameters.&quot;,\"\n        errorLine2=\"                   ~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MustUseNamedParamsDetector.kt\"\n            line=\"59\"\n            column=\"20\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Use Kotlin&apos;s $FQN_KOTLIN_PAIR instead of other Pair types from other libraries like AndroidX and Slack commons&quot;,\"\n        errorLine2=\"        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/NonKotlinPairDetector.kt\"\n            line=\"95\"\n            column=\"9\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Use Kotlin&apos;s $FQN_KOTLIN_PAIR instead of other Pair types from other libraries like AndroidX and Slack commons&quot;,\"\n        errorLine2=\"        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/NonKotlinPairDetector.kt\"\n            line=\"95\"\n            column=\"9\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;ConcurrentHashMap&quot; looks like a code reference; surround with backtics in string to display as symbol, e.g. \\`ConcurrentHashMap\\`\"\n        errorLine1=\"          context.report(ISSUE, location, &quot;ConcurrentHashMap should not use nullable $name types&quot;)\"\n        errorLine2=\"                                           ~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/NullableConcurrentHashMapDetector.kt\"\n            line=\"65\"\n            column=\"44\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;ConcurrentHashMap&quot; looks like a code reference; surround with backtics in string to display as symbol, e.g. \\`ConcurrentHashMap\\`\"\n        errorLine1=\"          context.report(ISSUE, location, &quot;ConcurrentHashMap should not use nullable $name types&quot;)\"\n        errorLine2=\"                                           ~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/NullableConcurrentHashMapDetector.kt\"\n            line=\"111\"\n            column=\"44\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;ConcurrentHashMap&quot; looks like a code reference; surround with backtics in string to display as symbol, e.g. \\`ConcurrentHashMap\\`\"\n        errorLine1=\"        ConcurrentHashMap does not support null keys or values. \\\"\n        errorLine2=\"        ~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/NullableConcurrentHashMapDetector.kt\"\n            line=\"137\"\n            column=\"9\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"        &quot;Use SlackDispatchers.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/RawDispatchersUsageDetector.kt\"\n            line=\"40\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"        &quot;Methods annotated with @RestrictedCallsTo should only be called from the specified scope.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/RestrictCallsToDetector.kt\"\n            line=\"27\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;ok&quot; is usually capitalized as &quot;OK&quot;\"\n        errorLine1=\"          ok.\"\n        errorLine2=\"          ~~\">\n        <location\n            file=\"src/main/java/slack/lint/RestrictCallsToDetector.kt\"\n            line=\"31\"\n            column=\"11\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"        &quot;This is replaced by the caller.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/retrofit/RetrofitUsageDetector.kt\"\n            line=\"176\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"        briefDescription = &quot;subscribeOn called with the main thread scheduler.&quot;,\"\n        errorLine2=\"                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"43\"\n            column=\"29\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be capitalized\"\n        errorLine1=\"        briefDescription = &quot;subscribeOn called with the main thread scheduler.&quot;,\"\n        errorLine2=\"                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"43\"\n            column=\"29\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"Multi-line issue explanation strings will interpret line separators as hard breaks, and this looks like a continuation of the same paragraph. Consider using \\ at the end of the previous line to indicate that the lines should be joined, or add a blank line between unrelated sentences, or suppress this issue type here.\"\n        errorLine1=\"        on the main thread - that is, code above this line.\"\n        errorLine2=\"        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"47\"\n            column=\"9\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"        &quot;Don&apos;t use Serializable.&quot;,\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/SerializableDetector.kt\"\n            line=\"47\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should be shorter; typically just a 3-6 words; it&apos;s used as a topic header in HTML reports and in the IDE inspections window\"\n        errorLine1=\"          &quot;Check that Span flags use the bitwise mask SPAN_POINT_MARK_MASK when being compared to.&quot;,\"\n        errorLine2=\"           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/text/SpanMarkPointMissingMaskDetector.kt\"\n            line=\"31\"\n            column=\"12\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"The issue summary should *not* end with a period (think of it as a headline)\"\n        errorLine1=\"          &quot;Wrong import alias for this R class.&quot;,\"\n        errorLine2=\"           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/resources/WrongResourceImportAliasDetector.kt\"\n            line=\"127\"\n            column=\"12\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"Multi-line issue explanation strings will interpret line separators as hard breaks, and this looks like a continuation of the same paragraph. Consider using \\ at the end of the previous line to indicate that the lines should be joined, or add a blank line between unrelated sentences, or suppress this issue type here.\"\n        errorLine1=\"            &quot;import slack.l10n.R as L10nR\\n&quot; +\"\n        errorLine2=\"             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/resources/WrongResourceImportAliasDetector.kt\"\n            line=\"129\"\n            column=\"14\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplTextFormat\"\n        message=\"&quot;UiKit&quot; looks like a code reference; surround with backtics in string to display as symbol, e.g. \\`UiKit\\`\"\n        errorLine1=\"            &quot;import slack.uikit.R as UiKitR&quot;,\"\n        errorLine2=\"                                     ~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/resources/WrongResourceImportAliasDetector.kt\"\n            line=\"130\"\n            column=\"38\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplUseUast\"\n        message=\"Don&apos;t call PsiField#getInitializer(); you must use UAST instead. If you don&apos;t have a UField call UastFacade.getInitializerBody(field)\"\n        errorLine1=\"      val assignment = variable?.initializer as? PsiMethodCallExpression\"\n        errorLine2=\"                       ~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"90\"\n            column=\"24\"/>\n    </issue>\n\n    <issue\n        id=\"LintImplUseUast\"\n        message=\"Don&apos;t call PsiField#getInitializer(); you must use UAST instead. If you don&apos;t have a UField call UastFacade.getInitializerBody(field)\"\n        errorLine1=\"              val initializer = reference.initializer\"\n        errorLine2=\"                                ~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"134\"\n            column=\"33\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"              containingClass.classKind == JvmClassKind.CLASS -> {\"\n        errorLine2=\"              ~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/DaggerIssuesDetector.kt\"\n            line=\"176\"\n            column=\"15\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"              containingClass.classKind == JvmClassKind.INTERFACE &amp;&amp; isProvides -> {\"\n        errorLine2=\"              ~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/DaggerIssuesDetector.kt\"\n            line=\"195\"\n            column=\"15\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"                  node.parameterList\"\n        errorLine2=\"                  ~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/DaggerIssuesDetector.kt\"\n            line=\"212\"\n            column=\"19\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"            val nodeLocation = node.returnTypeElement ?: node\"\n        errorLine2=\"                               ~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/DaggerIssuesDetector.kt\"\n            line=\"231\"\n            column=\"32\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"            if (firstParam.isReceiver()) {\"\n        errorLine2=\"                ~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/DaggerIssuesDetector.kt\"\n            line=\"242\"\n            column=\"17\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          return containingClass.name?.endsWith(&quot;Repository&quot;) == true\"\n        errorLine2=\"                 ~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/eithernet/DoNotExposeEitherNetInRepositoriesDetector.kt\"\n            line=\"94\"\n            column=\"18\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"    constructors.any {\"\n        errorLine2=\"    ~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/FragmentDaggerFieldInjectionDetector.kt\"\n            line=\"56\"\n            column=\"5\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        checker.check(node, node.name, &quot;class&quot;)\"\n        errorLine2=\"                            ~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/inclusive/InclusiveNamingSourceCodeScanner.kt\"\n            line=\"52\"\n            column=\"29\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        val type = if (isKotlin(node.language)) &quot;function&quot; else &quot;method&quot;\"\n        errorLine2=\"                                ~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/inclusive/InclusiveNamingSourceCodeScanner.kt\"\n            line=\"57\"\n            column=\"33\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"              if (isKotlin(node.language)) {\"\n        errorLine2=\"                           ~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/inclusive/InclusiveNamingSourceCodeScanner.kt\"\n            line=\"66\"\n            column=\"28\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        if (!containingClass.implements(ITEM_DECORATION_CLASS_NAME)) return\"\n        errorLine2=\"             ~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ui/ItemDecorationViewBindingDetector.kt\"\n            line=\"32\"\n            column=\"14\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          node.baseClassType.resolve()?.let { psiClass ->\"\n        errorLine2=\"          ~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/JavaOnlyDetector.kt\"\n            line=\"91\"\n            column=\"11\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        return listOfNotNull(node.javaPsi.superClass, *node.interfaces)\"\n        errorLine2=\"                                                       ~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/JavaOnlyDetector.kt\"\n            line=\"116\"\n            column=\"56\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"              val source = node.text\"\n        errorLine2=\"                           ~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/JavaOnlyDetector.kt\"\n            line=\"121\"\n            column=\"28\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        return context.evaluator.getSuperMethod(node)?.let { method ->\"\n        errorLine2=\"                                                ~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/JavaOnlyDetector.kt\"\n            line=\"184\"\n            column=\"49\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"            val modifier = node.modifierList.children.joinToString(separator = &quot; &quot;) { it.text }\"\n        errorLine2=\"                           ~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/JavaOnlyDetector.kt\"\n            line=\"192\"\n            column=\"28\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"  containingClass ?: return false\"\n        errorLine2=\"  ~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/util/LintUtils.kt\"\n            line=\"81\"\n            column=\"3\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"  if (isKotlin(language) &amp;&amp; evaluator.hasModifier(this, KtTokens.INNER_KEYWORD)) return true\"\n        errorLine2=\"               ~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/util/LintUtils.kt\"\n            line=\"87\"\n            column=\"16\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"  if (language == KotlinLanguage.INSTANCE &amp;&amp; context.evaluator.isSuspend(this)) {\"\n        errorLine2=\"      ~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/util/LintUtils.kt\"\n            line=\"293\"\n            column=\"7\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"    val classReference = parameterList.parameters.lastOrNull()?.type as? PsiClassType ?: return null\"\n        errorLine2=\"                         ~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/util/LintUtils.kt\"\n            line=\"294\"\n            column=\"26\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        if (uClass.classKind in applicableClassKinds) {\"\n        errorLine2=\"            ~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/util/MetadataJavaEvaluator.kt\"\n            line=\"165\"\n            column=\"13\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        if (cls.classKind in applicableClassKinds) {\"\n        errorLine2=\"            ~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/util/MetadataJavaEvaluator.kt\"\n            line=\"192\"\n            column=\"13\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        if (isKotlin(node.language)) {\"\n        errorLine2=\"                     ~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/mocking/MockDetector.kt\"\n            line=\"182\"\n            column=\"22\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        } else if (isJava(node.language) &amp;&amp; isMockAnnotated(node)) {\"\n        errorLine2=\"                          ~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/mocking/MockDetector.kt\"\n            line=\"189\"\n            column=\"27\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        if (node.isEnum) {\"\n        errorLine2=\"            ~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"76\"\n            column=\"13\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          slackEvaluator.isObject(node) ||\"\n        errorLine2=\"                                  ~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"199\"\n            column=\"35\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          if (slackEvaluator.isObject(node)) {\"\n        errorLine2=\"                                      ~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"206\"\n            column=\"39\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          node.constructors\"\n        errorLine2=\"          ~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"234\"\n            column=\"11\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"                    .name(&quot;Make ${parameter.name} &apos;val&apos;&quot;)\"\n        errorLine2=\"                                  ~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"303\"\n            column=\"35\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"                    .name(&quot;Make ${parameter.name} &apos;internal&apos;&quot;)\"\n        errorLine2=\"                                  ~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"323\"\n            column=\"35\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        val typeLabelAnnotation = node.getAnnotation(FQCN_TYPE_LABEL)\"\n        errorLine2=\"                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"446\"\n            column=\"35\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        val defaultObjectAnnotation = node.getAnnotation(FQCN_DEFAULT_OBJECT)\"\n        errorLine2=\"                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"447\"\n            column=\"39\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        if (isTypeLabeled &amp;&amp; node.hasTypeParameters()) {\"\n        errorLine2=\"                             ~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"467\"\n            column=\"30\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          node.superTypes\"\n        errorLine2=\"          ~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"483\"\n            column=\"11\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          &quot;Could not load class for ${psiType.className} on ${parameter.getUastParentOfType&lt;UClass>()!!.name}.${parameter.name}&quot;\"\n        errorLine2=\"                                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"616\"\n            column=\"63\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          &quot;Could not load class for ${psiType.className} on ${parameter.getUastParentOfType&lt;UClass>()!!.name}.${parameter.name}&quot;\"\n        errorLine2=\"                                                                                                                ~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"616\"\n            column=\"113\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        jsonName == member.name -> {\"\n        errorLine2=\"                    ~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"833\"\n            column=\"21\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        context.getNameLocation(member as PsiElement),\"\n        errorLine2=\"                                ~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"854\"\n            column=\"33\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        &quot;Name &apos;$jsonName&apos; is duplicated by member &apos;${member.name}&apos;.&quot;,\"\n        errorLine2=\"                                                     ~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"860\"\n            column=\"54\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"    val isKotlin = isKotlin(node.language)\"\n        errorLine2=\"                            ~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"912\"\n            column=\"29\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"    val unknownIndex = constants.indexOfFirst { it.name == &quot;UNKNOWN&quot; }\"\n        errorLine2=\"                                                ~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"1069\"\n            column=\"49\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        val jsonAnnotation = constant.getAnnotation(FQCN_JSON)\"\n        errorLine2=\"                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"1087\"\n            column=\"30\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"      val name = constant.name\"\n        errorLine2=\"                 ~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"1115\"\n            column=\"18\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          node.constructors\"\n        errorLine2=\"          ~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/parcel/ParcelizeFunctionPropertyDetector.kt\"\n            line=\"40\"\n            column=\"11\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"            it.findSuperMethods()[0].toUElementOfType()\"\n        errorLine2=\"            ~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/RestrictCallsToDetector.kt\"\n            line=\"76\"\n            column=\"13\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"              val currentText = node.text\"\n        errorLine2=\"                                ~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/retrofit/RetrofitUsageDetector.kt\"\n            line=\"93\"\n            column=\"33\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"        if (node.isEnum) return\"\n        errorLine2=\"            ~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/SerializableDetector.kt\"\n            line=\"23\"\n            column=\"13\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          node.implements(&quot;java.io.Serializable&quot;) { fqcn ->\"\n        errorLine2=\"          ~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/SerializableDetector.kt\"\n            line=\"25\"\n            column=\"11\"/>\n    </issue>\n\n    <issue\n        id=\"UElementAsPsi\"\n        message=\"Do not use `UElement` as `PsiElement`\"\n        errorLine1=\"          if (node.implements(&quot;android.os.Parcelable&quot;)) return\"\n        errorLine2=\"              ~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/SerializableDetector.kt\"\n            line=\"31\"\n            column=\"15\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`, `UElementWithLocation`\"\n        errorLine1=\"import org.jetbrains.uast.java.JavaUCallExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"19\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCompositeQualifiedExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UQualifiedReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"import org.jetbrains.uast.java.JavaUCompositeQualifiedExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"20\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`\"\n        errorLine1=\"import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"21\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UQualifiedReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"import org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"22\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCompositeQualifiedExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UQualifiedReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"            is JavaUCompositeQualifiedExpression ->\"\n        errorLine2=\"               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"53\"\n            column=\"16\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`, `UElementWithLocation`\"\n        errorLine1=\"              checkCall { JavaUCallExpression::class.safeCast(arg.selector) }\"\n        errorLine2=\"                          ~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"54\"\n            column=\"27\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`, `UElementWithLocation`\"\n        errorLine1=\"            is JavaUCallExpression -> checkCall { arg }\"\n        errorLine2=\"               ~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"55\"\n            column=\"16\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UQualifiedReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"            is KotlinUQualifiedReferenceExpression ->\"\n        errorLine2=\"               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"56\"\n            column=\"16\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`\"\n        errorLine1=\"              checkCall { KotlinUFunctionCallExpression::class.safeCast(arg.selector) }\"\n        errorLine2=\"                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"57\"\n            column=\"27\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`\"\n        errorLine1=\"            is KotlinUFunctionCallExpression -> checkCall { arg }\"\n        errorLine2=\"               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt\"\n            line=\"58\"\n            column=\"16\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUAnnotation is UAST implementation. Consider using one of its corresponding UAST interfaces: `UAnnotationEx`, `UAnnotation`, `UResolvable`, `UAnchorOwner`\"\n        errorLine1=\"import org.jetbrains.uast.kotlin.KotlinUAnnotation\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"48\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUClassLiteralExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UClassLiteralExpression`\"\n        errorLine1=\"import org.jetbrains.uast.kotlin.KotlinUClassLiteralExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"49\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUAnnotation is UAST implementation. Consider using one of its corresponding UAST interfaces: `UAnnotationEx`, `UAnnotation`, `UResolvable`, `UAnchorOwner`\"\n        errorLine1=\"                (annotationEntry.toUElement() as KotlinUAnnotation).takeIf {\"\n        errorLine2=\"                                                 ~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"363\"\n            column=\"50\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUClassLiteralExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UClassLiteralExpression`\"\n        errorLine1=\"      (adaptedByAnnotation.findAttributeValue(&quot;adapter&quot;) as? KotlinUClassLiteralExpression)\"\n        errorLine2=\"                                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/MoshiUsageDetector.kt\"\n            line=\"545\"\n            column=\"62\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`, `UElementWithLocation`\"\n        errorLine1=\"import org.jetbrains.uast.java.JavaUCallExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"24\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCompositeQualifiedExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UQualifiedReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"import org.jetbrains.uast.java.JavaUCompositeQualifiedExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"25\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`\"\n        errorLine1=\"import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"26\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UQualifiedReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"import org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"27\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `USimpleNameReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"import org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"28\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.psi.UastKotlinPsiVariable is UAST implementation. Consider using one of its corresponding UAST interfaces.\"\n        errorLine1=\"import org.jetbrains.uast.kotlin.psi.UastKotlinPsiVariable\"\n        errorLine2=\"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"29\"\n            column=\"1\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCompositeQualifiedExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UQualifiedReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"      is JavaUCompositeQualifiedExpression ->\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"75\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`, `UElementWithLocation`\"\n        errorLine1=\"        checkCall { JavaUCallExpression::class.safeCast(arg.selector) }\"\n        errorLine2=\"                    ~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"76\"\n            column=\"21\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.java.JavaUCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`, `UElementWithLocation`\"\n        errorLine1=\"      is JavaUCallExpression -> checkCall { arg }\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"77\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UQualifiedReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"      is KotlinUQualifiedReferenceExpression ->\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"78\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`\"\n        errorLine1=\"        checkCall { KotlinUFunctionCallExpression::class.safeCast(arg.selector) }\"\n        errorLine2=\"                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"79\"\n            column=\"21\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `UCallExpression`, `UResolvable`\"\n        errorLine1=\"      is KotlinUFunctionCallExpression -> checkCall { arg }\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"80\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression is UAST implementation. Consider using one of its corresponding UAST interfaces: `UExpression`, `UAnnotated`, `USimpleNameReferenceExpression`, `UReferenceExpression`, `UResolvable`\"\n        errorLine1=\"      is KotlinUSimpleReferenceExpression -> {\"\n        errorLine2=\"         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"126\"\n            column=\"10\"/>\n    </issue>\n\n    <issue\n        id=\"UastImplementation\"\n        message=\"org.jetbrains.uast.kotlin.psi.UastKotlinPsiVariable is UAST implementation. Consider using one of its corresponding UAST interfaces.\"\n        errorLine1=\"            is UastKotlinPsiVariable -> { // The variable reference is local\"\n        errorLine2=\"               ~~~~~~~~~~~~~~~~~~~~~\">\n        <location\n            file=\"src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt\"\n            line=\"133\"\n            column=\"16\"/>\n    </issue>\n\n</issues>\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/AlwaysNullReadOnlyVariableDetector.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport org.jetbrains.kotlin.lexer.KtTokens\nimport org.jetbrains.kotlin.psi.KtProperty\nimport org.jetbrains.kotlin.psi.KtReturnExpression\nimport org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType\nimport org.jetbrains.kotlin.psi.psiUtil.isNull\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.ULiteralExpression\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.UVariable\nimport org.jetbrains.uast.kotlin.isKotlin\nimport slack.lint.util.sourceImplementation\n\nclass AlwaysNullReadOnlyVariableDetector : Detector(), SourceCodeScanner {\n  override fun getApplicableUastTypes() =\n    listOf(UVariable::class.java, UCallExpression::class.java, UMethod::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    if (!isKotlin(context.uastFile?.lang)) return null\n\n    return object : UElementHandler() {\n\n      private fun isNullInitializedForReadOnlyVariable(node: UVariable): Boolean {\n        val uastInitializer = node.uastInitializer ?: return false\n        val isNullInitialized = uastInitializer is ULiteralExpression && uastInitializer.isNull\n        if (!isNullInitialized) return false\n        val sourcePsi = node.sourcePsi\n        val isReadOnlyVariable =\n          sourcePsi is KtProperty &&\n            !sourcePsi.isVar &&\n            !sourcePsi.hasModifier(KtTokens.OPEN_KEYWORD)\n        return isReadOnlyVariable\n      }\n\n      override fun visitVariable(node: UVariable) {\n        if (isNullInitializedForReadOnlyVariable(node)) {\n          context.report(\n            ISSUE_ALWAYS_INITIALIZE_NULL,\n            context.getLocation(node.uastInitializer),\n            ISSUE_ALWAYS_INITIALIZE_NULL.getBriefDescription(TextFormat.TEXT),\n          )\n        }\n      }\n\n      override fun visitMethod(node: UMethod) {\n        val sourcePsi = node.sourcePsi?.parent as? KtProperty ?: return\n        val getter = sourcePsi.getter ?: return\n\n        val isReadOnlyVariable = !sourcePsi.isVar\n        if (isReadOnlyVariable) {\n\n          // get() = null\n          val bodyExpression = getter.bodyExpression ?: return\n\n          if (bodyExpression.isNull()) {\n            context.report(\n              ISSUE_ALWAYS_RETURN_NULL_IN_GETTER,\n              context.getLocation(bodyExpression),\n              ISSUE_ALWAYS_RETURN_NULL_IN_GETTER.getBriefDescription(TextFormat.TEXT),\n            )\n          }\n\n          // get() { return null }\n          val returnExpression = bodyExpression.collectDescendantsOfType<KtReturnExpression>()\n          returnExpression.forEach { expression ->\n            val returnedExpression = expression.returnedExpression ?: return@forEach\n            if (returnedExpression.isNull()) {\n              context.report(\n                ISSUE_ALWAYS_RETURN_NULL_IN_GETTER,\n                context.getLocation(returnedExpression),\n                ISSUE_ALWAYS_RETURN_NULL_IN_GETTER.getBriefDescription(TextFormat.TEXT),\n              )\n            }\n          }\n        }\n      }\n    }\n  }\n\n  companion object {\n    val ISSUE_ALWAYS_INITIALIZE_NULL: Issue =\n      Issue.create(\n        \"AvoidNullInitForReadOnlyVariables\",\n        \"Avoid initializing read-only variable with null in Kotlin\",\n        \"\"\"\n          Avoid unnecessary `null` initialization for read-only variables, as they can never be reassigned. \\\n          Assigning null explicitly does not provide any real benefit and may mislead readers into thinking the value could change later. \\\n          If the variable needs to be modified later, it's better to use `var` instead of `val`, or consider using `lateinit var` if it is guaranteed to be initialized before use.\n        \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.WARNING,\n        sourceImplementation<AlwaysNullReadOnlyVariableDetector>(),\n      )\n\n    val ISSUE_ALWAYS_RETURN_NULL_IN_GETTER: Issue =\n      Issue.create(\n        \"AvoidReturningNullInGetter\",\n        \"Avoid returning null in getter for read-only properties in Kotlin\",\n        \"\"\"\n          Avoid defining a getter that always returns `null` for a read-only (`val`) property. \\\n        Since `val` properties cannot be reassigned, having a getter that consistently returns `null` serves no real purpose \\\n        and may cause confusion.\n\n        If the value needs to be dynamically computed, ensure the getter returns a meaningful result. \\\n        Otherwise, consider using a function (`fun`) instead of a property.\n        \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.WARNING,\n        sourceImplementation<AlwaysNullReadOnlyVariableDetector>(),\n      )\n\n    val ISSUES: List<Issue> =\n      listOf(ISSUE_ALWAYS_INITIALIZE_NULL, ISSUE_ALWAYS_RETURN_NULL_IN_GETTER)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/AnnotatedClassOrMethodUsageDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.AbstractAnnotationDetector\nimport com.android.tools.lint.detector.api.AnnotationInfo\nimport com.android.tools.lint.detector.api.AnnotationUsageInfo\nimport com.android.tools.lint.detector.api.AnnotationUsageType\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport org.jetbrains.uast.UElement\n\n/**\n * Raises a warning whenever we use deprecated methods or classes. Generally used for keeping track\n * of health score.\n */\nabstract class AnnotatedClassOrMethodUsageDetector :\n  AbstractAnnotationDetector(), SourceCodeScanner {\n\n  abstract val annotationNames: List<String>\n  abstract val issue: Issue\n  open val isEnabled: Boolean = true\n\n  override fun applicableAnnotations(): List<String> =\n    if (isEnabled) annotationNames else emptyList()\n\n  override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean {\n    // If it's not enabled, no need to scan further\n    if (!isEnabled) return false\n    @Suppress(\"DEPRECATION\") // METHOD_CALL_CLASS doesn't have a replacement\n    return type == AnnotationUsageType.METHOD_CALL ||\n      type == AnnotationUsageType.METHOD_CALL_CLASS ||\n      type == AnnotationUsageType.METHOD_CALL_PARAMETER\n  }\n\n  override fun visitAnnotationUsage(\n    context: JavaContext,\n    element: UElement,\n    annotationInfo: AnnotationInfo,\n    usageInfo: AnnotationUsageInfo,\n  ) {\n    if (isEnabled && applicableAnnotations().contains(annotationInfo.qualifiedName)) {\n      val issueToReport = issue\n      val location = context.getLocation(element)\n      report(\n        context,\n        issueToReport,\n        element,\n        location,\n        issueToReport.getBriefDescription(TextFormat.TEXT),\n        null,\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/ArgInFormattedQuantityStringResDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.intellij.psi.PsiLocalVariable\nimport com.intellij.psi.PsiMethod\nimport com.intellij.psi.PsiMethodCallExpression\nimport kotlin.reflect.full.safeCast\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UExpression\nimport org.jetbrains.uast.java.JavaUCallExpression\nimport org.jetbrains.uast.java.JavaUCompositeQualifiedExpression\nimport org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression\nimport org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression\nimport org.jetbrains.uast.sourcePsiElement\n\nclass ArgInFormattedQuantityStringResDetector : Detector(), SourceCodeScanner {\n\n  companion object {\n    val ISSUE_ARG_IN_QUANTITY_STRING_FORMAT: Issue =\n      Issue.create(\n        \"ArgInFormattedQuantityStringRes\",\n        \"Count value in formatted string resource.\",\n        \"Some languages require modifiers to counted values in written text. Consider consulting #plz-localization \" +\n          \"if you are unsure if this formatted string requires a special modifier. If one is required, consider using \" +\n          \"`LocalizationUtils.getFormattedCount()`. If not, suppress this warning.\",\n        Category.I18N,\n        6,\n        Severity.WARNING,\n        Implementation(ArgInFormattedQuantityStringResDetector::class.java, Scope.JAVA_FILE_SCOPE),\n      )\n\n    val issues: List<Issue> = listOf(ISSUE_ARG_IN_QUANTITY_STRING_FORMAT)\n  }\n\n  override fun getApplicableMethodNames(): List<String> = listOf(\"getQuantityString\")\n\n  override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {\n    // Ignore methods that aren't in a subclass of Android \"Resources\"\n    if (context.evaluator.isMemberInSubClassOf(method, \"android.content.res.Resources\", false)) {\n      node.valueArguments\n        .drop(2) // Ignore the first 2 arguments passed to \"getQuantityString\"\n        .forEach { arg ->\n          when (arg) {\n            is JavaUCompositeQualifiedExpression ->\n              checkCall { JavaUCallExpression::class.safeCast(arg.selector) }\n            is JavaUCallExpression -> checkCall { arg }\n            is KotlinUQualifiedReferenceExpression ->\n              checkCall { KotlinUFunctionCallExpression::class.safeCast(arg.selector) }\n            is KotlinUFunctionCallExpression -> checkCall { arg }\n            else -> checkVariable { arg }\n          }.let { countFormatFound ->\n            if (!countFormatFound) {\n              context.report(\n                ISSUE_ARG_IN_QUANTITY_STRING_FORMAT,\n                context.getLocation(arg),\n                \"This may require a localized count modifier. If so, use `LocalizationUtils.getFormattedCount()`. Consult #plz-localization if you are unsure.\",\n              )\n            }\n          }\n        }\n    }\n\n    super.visitMethodCall(context, node, method)\n  }\n\n  /**\n   * return true if the resolved [UCallExpression] has method name \"getFormattedCount\", false\n   * otherwise\n   */\n  private fun checkCall(fn: () -> UCallExpression?): Boolean {\n    return fn()?.let { call -> \"getFormattedCount\" == call.methodName } ?: false\n  }\n\n  /**\n   * return true if the resolved [UExpression] was created from the \"getFormattedCount\" method,\n   * false otherwise\n   */\n  private fun checkVariable(fn: () -> UExpression?): Boolean {\n    return fn()?.let { exp ->\n      val variable = exp.sourcePsiElement?.reference?.resolve() as? PsiLocalVariable\n      val assignment = variable?.initializer as? PsiMethodCallExpression\n      return assignment?.resolveMethod()?.name == \"getFormattedCount\"\n    } ?: false\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/CircuitScreenDataClassDetector.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.getUMethod\nimport org.jetbrains.kotlin.analysis.utils.isLocalClass\nimport org.jetbrains.kotlin.lexer.KtTokens\nimport org.jetbrains.kotlin.psi.KtClass\nimport org.jetbrains.kotlin.psi.KtClassOrObject\nimport org.jetbrains.kotlin.psi.KtObjectDeclaration\nimport org.jetbrains.kotlin.psi.KtPrimaryConstructor\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UElement\nimport slack.lint.util.implements\nimport slack.lint.util.sourceImplementation\n\nclass CircuitScreenDataClassDetector : Detector(), SourceCodeScanner {\n  override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UClass::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n\n      override fun visitClass(node: UClass) {\n        val sourceNode = node.sourcePsi as? KtClassOrObject\n\n        if (\n          sourceNode != null &&\n            !sourceNode.isData() &&\n            !node.isInterface &&\n            !sourceNode.hasModifier(\n              KtTokens.OPEN_KEYWORD\n            ) && // Open classes cannot be \"data\" classes\n            !sourceNode.hasModifier(\n              KtTokens.INNER_KEYWORD\n            ) && // Screens must be parcelable and inner classes cannot be parcelable\n            !sourceNode.hasModifier(\n              KtTokens.ABSTRACT_KEYWORD\n            ) && // Cannot have abstract data class / object\n            !sourceNode.hasModifier(\n              KtTokens.SEALED_KEYWORD\n            ) && // Cannot have sealed data class / object\n            !sourceNode.hasModifier(\n              KtTokens.COMPANION_KEYWORD\n            ) && // Cannot have companion data object\n            !node.isLocalClass() &&\n            node.implements(QUALIFIED_CIRCUIT_SCREEN)\n        ) {\n          val hasProperties =\n            !node.constructors\n              .asSequence()\n              .mapNotNull { it.getUMethod() }\n              .firstOrNull { it.sourcePsi is KtPrimaryConstructor }\n              ?.uastParameters\n              .isNullOrEmpty()\n          val classKeyword =\n            when (sourceNode) {\n              is KtClass -> sourceNode.getClassKeyword()\n              is KtObjectDeclaration -> sourceNode.getObjectKeyword() ?: return\n              else -> return\n            }\n          val isObject = classKeyword?.node?.elementType == KtTokens.OBJECT_KEYWORD\n          val originalKeyword = if (isObject) KtTokens.OBJECT_KEYWORD else KtTokens.CLASS_KEYWORD\n          val replacement =\n            if (hasProperties) \"${KtTokens.DATA_KEYWORD} ${KtTokens.CLASS_KEYWORD}\"\n            else \"${KtTokens.DATA_KEYWORD} ${KtTokens.OBJECT_KEYWORD}\"\n          val keywordLocation = context.getLocation(classKeyword)\n          val quickFix =\n            fix()\n              .replace()\n              .name(\"Replace with $replacement\")\n              .range(keywordLocation)\n              .text(originalKeyword.value)\n              .with(replacement)\n              .reformat(true)\n              .build()\n          context.report(ISSUE, keywordLocation, MESSAGE, quickFix)\n        }\n      }\n    }\n  }\n\n  companion object {\n    const val QUALIFIED_CIRCUIT_SCREEN = \"com.slack.circuit.runtime.screen.Screen\"\n    const val MESSAGE =\n      \"Circuit Screen implementations should be data classes or data objects, not regular classes.\"\n    const val ISSUE_ID = \"CircuitScreenShouldBeDataClass\"\n    const val BRIEF_DESCRIPTION = \"Circuit Screen should be a data class or data object\"\n    const val EXPLANATION =\n      \"\"\"Circuit Screen implementations should be data classes or data objects to ensure proper\nequality, hashCode, and toString implementations. Regular classes can cause issues with\nscreen comparison and navigation.\"\"\"\n\n    val ISSUE: Issue =\n      Issue.create(\n        ISSUE_ID,\n        BRIEF_DESCRIPTION,\n        EXPLANATION,\n        Category.CORRECTNESS,\n        8,\n        Severity.ERROR,\n        sourceImplementation<CircuitScreenDataClassDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/DaggerIssuesDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.android.tools.lint.detector.api.isDuplicatedOverload\nimport com.android.tools.lint.detector.api.isReceiver\nimport com.intellij.lang.jvm.JvmClassKind\nimport com.intellij.psi.PsiTypes\nimport org.jetbrains.kotlin.lexer.KtTokens\nimport org.jetbrains.uast.UAnnotated\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.getContainingUClass\nimport slack.lint.util.sourceImplementation\n\n/** This is a simple lint check to catch common Dagger+Kotlin usage issues. */\nclass DaggerIssuesDetector : Detector(), SourceCodeScanner {\n\n  companion object {\n    private val ISSUE_BINDS_MUST_BE_IN_MODULE: Issue =\n      Issue.create(\n        \"MustBeInModule\",\n        \"@Binds/@Provides functions must be in modules\",\n        \"@Binds/@Provides functions must be in `@Module`-annotated classes.\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<DaggerIssuesDetector>(),\n      )\n\n    private val ISSUE_BINDS_TYPE_MISMATCH: Issue =\n      Issue.create(\n        \"BindsTypeMismatch\",\n        \"@Binds parameter/return must be type-assignable\",\n        \"@Binds function parameters must be type-assignable to their return types.\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<DaggerIssuesDetector>(),\n      )\n\n    private val ISSUE_RETURN_TYPE: Issue =\n      Issue.create(\n        \"BindingReturnType\",\n        \"@Binds/@Provides must have a return type\",\n        \"@Binds/@Provides functions must have a return type. Cannot be void or Unit.\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<DaggerIssuesDetector>(),\n      )\n\n    private val ISSUE_RECEIVER_PARAMETER: Issue =\n      Issue.create(\n        \"BindingReceiverParameter\",\n        \"@Binds/@Provides functions cannot be extensions\",\n        \"@Binds/@Provides functions cannot be extension functions. Move the receiver type to a parameter via IDE inspection (option+enter and convert to parameter).\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<DaggerIssuesDetector>(),\n      )\n\n    private val ISSUE_BINDS_WRONG_PARAMETER_COUNT: Issue =\n      Issue.create(\n        \"BindsWrongParameterCount\",\n        \"@Binds must have one parameter\",\n        \"@Binds functions require a single parameter as an input to bind.\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<DaggerIssuesDetector>(),\n      )\n\n    private val ISSUE_BINDS_MUST_BE_ABSTRACT: Issue =\n      Issue.create(\n        \"BindsMustBeAbstract\",\n        \"@Binds functions must be abstract\",\n        \"@Binds functions must be abstract and cannot have function bodies.\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<DaggerIssuesDetector>(),\n      )\n\n    private val ISSUE_PROVIDES_CANNOT_BE_ABSTRACT: Issue =\n      Issue.create(\n        \"ProvidesMustNotBeAbstract\",\n        \"@Provides functions cannot be abstract\",\n        \"@Provides functions cannot be abstract.\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<DaggerIssuesDetector>(),\n      )\n\n    private val ISSUE_BINDS_REDUNDANT: Issue =\n      Issue.create(\n        \"RedundantBinds\",\n        \"@Binds functions should return a different type\",\n        \"@Binds functions should return a different type (including annotations) than the input type.\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<DaggerIssuesDetector>(),\n      )\n\n    private const val BINDS_ANNOTATION = \"dagger.Binds\"\n    private const val PROVIDES_ANNOTATION = \"dagger.Provides\"\n\n    val ISSUES: List<Issue> =\n      listOf(\n        ISSUE_BINDS_TYPE_MISMATCH,\n        ISSUE_RETURN_TYPE,\n        ISSUE_RECEIVER_PARAMETER,\n        ISSUE_BINDS_WRONG_PARAMETER_COUNT,\n        ISSUE_BINDS_MUST_BE_ABSTRACT,\n        ISSUE_BINDS_REDUNDANT,\n        ISSUE_BINDS_MUST_BE_IN_MODULE,\n        ISSUE_PROVIDES_CANNOT_BE_ABSTRACT,\n      )\n  }\n\n  override fun getApplicableUastTypes() = listOf(UMethod::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitMethod(node: UMethod) {\n        if (node.isDuplicatedOverload()) {\n          return\n        }\n        if (!node.isConstructor) {\n          val isBinds = node.hasAnnotation(BINDS_ANNOTATION)\n          val isProvides = node.hasAnnotation(PROVIDES_ANNOTATION)\n\n          if (!isBinds && !isProvides) return\n\n          val containingClass = node.getContainingUClass()\n          if (containingClass != null) {\n            // Fine to not use MetadataJavaEvaluator since we only care about current module\n            val moduleClass =\n              if (context.evaluator.hasModifier(containingClass, KtTokens.COMPANION_KEYWORD)) {\n                checkNotNull(containingClass.getContainingUClass()) {\n                  \"Companion object must be nested in a class\"\n                }\n              } else {\n                containingClass\n              }\n            when {\n              !moduleClass.hasAnnotation(\"dagger.Module\") -> {\n                context.report(\n                  ISSUE_BINDS_MUST_BE_IN_MODULE,\n                  context.getLocation(node),\n                  ISSUE_BINDS_MUST_BE_IN_MODULE.getBriefDescription(TextFormat.TEXT),\n                )\n                return\n              }\n              isBinds && containingClass.isInterface -> {\n                // Cannot have a default impl in interfaces\n                if (node.uastBody != null) {\n                  context.report(\n                    ISSUE_BINDS_MUST_BE_ABSTRACT,\n                    context.getLocation(node.uastBody),\n                    ISSUE_BINDS_MUST_BE_ABSTRACT.getBriefDescription(TextFormat.TEXT),\n                  )\n                  return\n                }\n              }\n              containingClass.classKind == JvmClassKind.CLASS -> {\n                val isAbstract = context.evaluator.isAbstract(node)\n                // Binds must be abstract\n                if (isBinds && !isAbstract) {\n                  context.report(\n                    ISSUE_BINDS_MUST_BE_ABSTRACT,\n                    context.getLocation(node),\n                    ISSUE_BINDS_MUST_BE_ABSTRACT.getBriefDescription(TextFormat.TEXT),\n                  )\n                  return\n                } else if (isProvides && isAbstract) {\n                  context.report(\n                    ISSUE_PROVIDES_CANNOT_BE_ABSTRACT,\n                    context.getLocation(node),\n                    ISSUE_PROVIDES_CANNOT_BE_ABSTRACT.getBriefDescription(TextFormat.TEXT),\n                  )\n                  return\n                }\n              }\n              containingClass.classKind == JvmClassKind.INTERFACE && isProvides -> {\n                context.report(\n                  ISSUE_PROVIDES_CANNOT_BE_ABSTRACT,\n                  context.getLocation(node),\n                  ISSUE_PROVIDES_CANNOT_BE_ABSTRACT.getBriefDescription(TextFormat.TEXT),\n                )\n                return\n              }\n            }\n          }\n\n          if (isBinds) {\n            if (node.uastParameters.size != 1) {\n              val locationToHighlight =\n                if (node.uastParameters.isEmpty()) {\n                  node\n                } else {\n                  node.parameterList\n                }\n              context.report(\n                ISSUE_BINDS_WRONG_PARAMETER_COUNT,\n                context.getLocation(locationToHighlight),\n                ISSUE_BINDS_WRONG_PARAMETER_COUNT.getBriefDescription(TextFormat.TEXT),\n              )\n              return\n            }\n          }\n\n          val returnType =\n            node.returnType?.takeUnless {\n              it == PsiTypes.voidType() ||\n                context.evaluator.getTypeClass(it)?.qualifiedName == \"kotlin.Unit\"\n            }\n\n          if (returnType == null) {\n            // Report missing return type\n            val nodeLocation = node.returnTypeElement ?: node\n            context.report(\n              ISSUE_RETURN_TYPE,\n              context.getLocation(nodeLocation),\n              ISSUE_RETURN_TYPE.getBriefDescription(TextFormat.TEXT),\n            )\n            return\n          }\n\n          if (node.uastParameters.isNotEmpty()) {\n            val firstParam = node.uastParameters[0]\n            if (firstParam.isReceiver()) {\n              context.report(\n                ISSUE_RECEIVER_PARAMETER,\n                context.getNameLocation(firstParam),\n                ISSUE_RECEIVER_PARAMETER.getBriefDescription(TextFormat.TEXT),\n              )\n              return\n            }\n\n            if (isBinds) {\n              val instanceType = firstParam.type\n              if (instanceType == returnType) {\n                // Check that they have different annotations, otherwise it's redundant\n                if (firstParam.qualifiers() == node.qualifiers()) {\n                  context.report(\n                    ISSUE_BINDS_REDUNDANT,\n                    context.getLocation(node),\n                    ISSUE_BINDS_REDUNDANT.getBriefDescription(TextFormat.TEXT),\n                  )\n                  return\n                }\n              }\n\n              if (!returnType.isAssignableFrom(instanceType)) {\n                context.report(\n                  ISSUE_BINDS_TYPE_MISMATCH,\n                  context.getLocation(node),\n                  ISSUE_BINDS_TYPE_MISMATCH.getBriefDescription(TextFormat.TEXT),\n                )\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  private fun UAnnotated.qualifiers() =\n    uAnnotations\n      .asSequence()\n      .filter { it.resolve()?.hasAnnotation(\"javax.inject.Qualifier\") == true }\n      .toSet()\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/DeprecatedAnnotationDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.LintClient\nimport com.android.tools.lint.detector.api.AnnotationInfo\nimport com.android.tools.lint.detector.api.AnnotationUsageInfo\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.UastLintUtils\nimport org.jetbrains.uast.UElement\nimport slack.lint.util.Priorities\nimport slack.lint.util.sourceImplementation\n\nprivate const val DEPRECATED_ANNOTATION_NAME_JAVA = \"java.lang.Deprecated\"\nprivate const val DEPRECATED_ANNOTATION_NAME_KOTLIN = \"kotlin.Deprecated\"\nprivate const val BRIEF_DESCRIPTION_PREFIX_DEFAULT = \"This class or method\"\nprivate const val BRIEF_DESCRIPTION_SUFFIX = \" is deprecated; consider using an alternative.\"\n\n/**\n * Raises a warning whenever we use deprecated methods or classes. Generally used for keeping track\n * of health score.\n */\nclass DeprecatedAnnotationDetector : AnnotatedClassOrMethodUsageDetector() {\n\n  override val annotationNames =\n    listOf(DEPRECATED_ANNOTATION_NAME_KOTLIN, DEPRECATED_ANNOTATION_NAME_JAVA)\n  override val issue = ISSUE_DEPRECATED_CALL\n\n  // Only enable on CLI\n  override val isEnabled: Boolean\n    get() = !LintClient.isStudio\n\n  override fun visitAnnotationUsage(\n    context: JavaContext,\n    element: UElement,\n    annotationInfo: AnnotationInfo,\n    usageInfo: AnnotationUsageInfo,\n  ) {\n    if (isEnabled && applicableAnnotations().contains(annotationInfo.qualifiedName)) {\n      val issueToReport = issue\n      val location = context.getLocation(element)\n      val messagePrefix =\n        usageInfo.referenced?.let(UastLintUtils.Companion::getQualifiedName)\n          ?: BRIEF_DESCRIPTION_PREFIX_DEFAULT\n      report(\n        context,\n        issueToReport,\n        element,\n        location,\n        messagePrefix + BRIEF_DESCRIPTION_SUFFIX,\n        null,\n      )\n    }\n  }\n\n  companion object {\n    private fun Implementation.toIssue(): Issue {\n      return Issue.create(\n        \"DeprecatedCall\",\n        BRIEF_DESCRIPTION_PREFIX_DEFAULT + BRIEF_DESCRIPTION_SUFFIX,\n        \"Using deprecated classes is not advised; please consider using an alternative.\",\n        Category.CORRECTNESS,\n        Priorities.NORMAL,\n        Severity.WARNING,\n        this,\n      )\n    }\n\n    val ISSUE_DEPRECATED_CALL = sourceImplementation<DeprecatedAnnotationDetector>().toIssue()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/DeprecatedSqlUsageDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport org.jetbrains.uast.UCallExpression\nimport slack.lint.util.sourceImplementation\n\n/**\n * Raises a warning on direct sqlite database usage and encourages\n * [SqlDelight](https://cashapp.github.io/sqldelight/) usage.\n */\n@Suppress(\"UnstableApiUsage\")\nclass DeprecatedSqlUsageDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext) =\n    object : UElementHandler() {\n      override fun visitCallExpression(node: UCallExpression) {\n        if (\n          APPLICABLE_RECEIVER_TYPES.contains(node.receiverType?.canonicalText) &&\n            APPLICABLE_CALL_NAMES.contains(node.methodIdentifier?.name)\n        ) {\n          context.report(\n            issue = ISSUE,\n            location = context.getLocation(node),\n            message = \"All SQL querying should be performed using `SqlDelight`\",\n          )\n        }\n      }\n    }\n\n  companion object {\n    private fun Implementation.toIssue(): Issue {\n      return Issue.create(\n        id = \"DeprecatedSqlUsage\",\n        briefDescription = \"Use SqlDelight!\",\n        explanation = \"Safer, faster, etc\",\n        category = Category.CORRECTNESS,\n        priority = 0,\n        severity = Severity.WARNING,\n        implementation = this,\n      )\n    }\n\n    val ISSUE: Issue = sourceImplementation<DeprecatedSqlUsageDetector>().toIssue()\n\n    private val APPLICABLE_CALL_NAMES = listOf(\"query\", \"insert\", \"update\", \"delete\", \"execSQL\")\n    private val APPLICABLE_RECEIVER_TYPES =\n      listOf(\"android.database.sqlite.SQLiteDatabase\", \"androidx.sqlite.db.SupportSQLiteDatabase\")\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/DoNotCallProvidersDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.getParentOfType\nimport slack.lint.util.sourceImplementation\n\nclass DoNotCallProvidersDetector : Detector(), SourceCodeScanner {\n\n  companion object {\n    private val SCOPES =\n      sourceImplementation<DoNotCallProvidersDetector>(shouldRunOnTestSources = false)\n\n    val ISSUE: Issue =\n      Issue.create(\n        \"DoNotCallProviders\",\n        \"Dagger provider methods should not be called directly by user code.\",\n        \"\"\"\n          Dagger provider methods should not be called directly by user code. These are intended solely for use \\\n          by Dagger-generated code and it is programmer error to call them from user code.\n      \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        SCOPES,\n      )\n\n    private val PROVIDER_ANNOTATIONS =\n      setOf(\"dagger.Binds\", \"dagger.Provides\", \"dagger.producers.Produces\")\n    private val GENERATED_ANNOTATIONS =\n      setOf(\"javax.annotation.Generated\", \"javax.annotation.processing.Generated\")\n  }\n\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext) =\n    object : UElementHandler() {\n      override fun visitCallExpression(node: UCallExpression) {\n        val enclosingClass = node.getParentOfType<UClass>() ?: return\n        if (GENERATED_ANNOTATIONS.any(enclosingClass::hasAnnotation)) return\n        val method = node.resolve() ?: return\n        if (PROVIDER_ANNOTATIONS.any(method::hasAnnotation)) {\n          context.report(\n            ISSUE,\n            context.getLocation(node),\n            ISSUE.getBriefDescription(TextFormat.TEXT),\n          )\n        }\n      }\n    }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/ExceptionMessageDetector.kt",
    "content": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage slack.lint\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.intellij.psi.PsiMethod\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.getParameterForArgument\nimport slack.lint.util.Name\nimport slack.lint.util.Package\nimport slack.lint.util.isInPackageName\nimport slack.lint.util.sourceImplementation\n\nclass ExceptionMessageDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableMethodNames(): List<String> =\n    listOf(Check, CheckNotNull, Require, RequireNotNull).map { it.shortName }\n\n  override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {\n\n    // We ignore other functions with the same name.\n    if (!method.isInPackageName(KotlinPackage)) return\n\n    val lazyMessage =\n      node.valueArguments.find { node.getParameterForArgument(it)?.name == \"lazyMessage\" }\n    if (lazyMessage == null) {\n      context.report(\n        ISSUE,\n        node,\n        context.getNameLocation(node),\n        \"Please specify a `lazyMessage` param for ${node.methodName}\",\n      )\n    }\n  }\n\n  internal companion object {\n    val KotlinPackage = Package(\"kotlin\")\n    val Check = Name(KotlinPackage, \"check\")\n    val CheckNotNull = Name(KotlinPackage, \"checkNotNull\")\n    val Require = Name(KotlinPackage, \"require\")\n    val RequireNotNull = Name(KotlinPackage, \"requireNotNull\")\n    val ISSUE =\n      Issue.create(\n        id = \"ExceptionMessage\",\n        briefDescription = \"Please provide a string for the `lazyMessage` parameter\",\n        explanation =\n          \"\"\"\n                Calls to `check()`, `checkNotNull()`, `require()` and `requireNotNull()` \\\n                should include a message string that can be used to debug issues \\\n                experienced by users.\n\n                When we read user-supplied logs, the line numbers are sometimes not\\\n                sufficient to determine the cause of a bug. Inline functions can\\\n                sometimes make it hard to determine which file threw an exception.\\\n                Consider supplying a `lazyMessage` parameter to identify the `check()`\\\n                or `require()` call.\n            \"\"\",\n        category = Category.CORRECTNESS,\n        priority = 3,\n        severity = Severity.ERROR,\n        implementation =\n          sourceImplementation<ExceptionMessageDetector>(shouldRunOnTestSources = false),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/FragmentDaggerFieldInjectionDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UField\nimport slack.lint.util.implements\nimport slack.lint.util.sourceImplementation\n\n/**\n * Detects that Fragments should use constructor injection in order to obtain references to its\n * dependencies.\n */\nclass FragmentDaggerFieldInjectionDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes() = listOf(UField::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n\n      override fun visitField(node: UField) {\n        if (node.isStatic || node.findAnnotation(FQN_JAVAX_INJECT) == null) return\n\n        val nodeParent = node.uastParent\n        if (nodeParent !is UClass || nodeParent.isInterface) return\n\n        if (!nodeParent.isFragment()) return\n\n        val issueToReport =\n          if (nodeParent.hasConstructorInjection()) {\n            ISSUE_FRAGMENT_CONSTRUCTOR_INJECTION_AVAILABLE\n          } else {\n            ISSUE_FRAGMENT_FIELD_INJECTION_USED\n          }\n\n        context.report(\n          issueToReport,\n          context.getLocation(node),\n          issueToReport.getBriefDescription(TextFormat.TEXT),\n        )\n      }\n    }\n  }\n\n  private fun UClass.isFragment() = implements(\"androidx.fragment.app.Fragment\")\n\n  private fun UClass.hasConstructorInjection() =\n    constructors.any {\n      it.hasAnnotation(FQN_JAVAX_INJECT) || it.hasAnnotation(FQN_DAGGER_ASSISTED_INJECT)\n    }\n\n  companion object {\n    private const val FQN_JAVAX_INJECT = \"javax.inject.Inject\"\n    private const val FQN_DAGGER_ASSISTED_INJECT = \"dagger.assisted.AssistedInject\"\n\n    private val ISSUE_FRAGMENT_CONSTRUCTOR_INJECTION_AVAILABLE: Issue =\n      Issue.create(\n        id = \"FragmentConstructorInjection\",\n        briefDescription =\n          \"Fragment dependencies should be injected using constructor injections only.\",\n        explanation =\n          \"\"\"\n        This Fragment has been set up to inject its dependencies through the constructor. \\\n        This dependency should be declared in the constructor where dagger will handle the \\\n        injection at runtime.\n      \"\"\",\n        category = Category.CORRECTNESS,\n        priority = 6,\n        severity = Severity.ERROR,\n        implementation = sourceImplementation<FragmentDaggerFieldInjectionDetector>(),\n      )\n\n    private val ISSUE_FRAGMENT_FIELD_INJECTION_USED: Issue =\n      Issue.create(\n        id = \"FragmentFieldInjection\",\n        briefDescription =\n          \"Fragment dependencies should be injected using the Fragment's constructor.\",\n        explanation =\n          \"\"\"\n        This dependency should be injected by dagger via the constructor. Add this field's type \\\n        into the parameter list of the Fragment's constructor. This constructor should be annotated \\\n        with either `@AssistedInject` or `@Inject`. Annotate with `@AssistedInject` if this \\\n        Fragment requires runtime arguments via a `Bundle`. Annotate with `@Inject` if this \\\n        Fragment does not require any runtime arguments. If this is an abstract class, the \\\n        constructor does not need to be annotated with `@Inject` or `@AssistedInject`.\n      \"\"\",\n        category = Category.CORRECTNESS,\n        priority = 6,\n        severity = Severity.ERROR,\n        implementation = sourceImplementation<FragmentDaggerFieldInjectionDetector>(),\n      )\n\n    val issues =\n      listOf(ISSUE_FRAGMENT_CONSTRUCTOR_INJECTION_AVAILABLE, ISSUE_FRAGMENT_FIELD_INJECTION_USED)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/GuavaPreconditionsDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.android.tools.lint.detector.api.isKotlin\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UQualifiedReferenceExpression\nimport slack.lint.util.sourceImplementation\n\n/**\n * Detect usages of Guava's Preconditions and recommend to use the JavaPreconditions that uses\n * Kotlin stdlib alternatives.\n */\nclass GuavaPreconditionsDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitCallExpression(node: UCallExpression) {\n        if (isUsingGuavaPreconditions(node)) {\n          if (isKotlin(node.lang)) {\n            reportKotlin(context, node)\n          } else {\n            reportJavaGuavaUsage(context, node)\n          }\n        }\n      }\n    }\n  }\n\n  private fun reportJavaGuavaUsage(context: JavaContext, node: UCallExpression) {\n    val issueToReport = ISSUE_GUAVA_CHECKS_USED\n\n    val lintFix =\n      fix()\n        .name(\"Use Slack's JavaPreconditions checks\")\n        .replace()\n        .shortenNames()\n        .range(context.getLocation(node))\n        .text(createLintFixTextReplaceString(node))\n        .with(\"$FQN_SLACK_JAVA_PRECONDITIONS.${node.methodName}\")\n        .autoFix()\n        .build()\n    reportIssue(context, node, issueToReport, lintFix)\n  }\n\n  private fun reportKotlin(context: JavaContext, node: UCallExpression) {\n    val issueToReport = ISSUE_GUAVA_PRECONDITIONS_USED_IN_KOTLIN\n\n    val updatedKotlinCheckMethod =\n      when (node.methodName) {\n        METHOD_GUAVA_CHECK_STATE -> METHOD_KOTLIN_CHECK_STATE\n        METHOD_GUAVA_CHECK_ARGUMENT -> METHOD_KOTLIN_CHECK_ARGUMENT\n        METHOD_GUAVA_CHECK_NOT_NULL -> METHOD_KOTLIN_CHECK_NOT_NULL\n        else -> null\n      }\n\n    val lintFix =\n      updatedKotlinCheckMethod?.let { updatedCheckMethod ->\n        fix()\n          .name(\"Use Kotlin's standard library checks\")\n          .replace()\n          .shortenNames()\n          .range(context.getLocation(node))\n          .text(createLintFixTextReplaceString(node))\n          .with(updatedCheckMethod)\n          .autoFix()\n          .build()\n      }\n    reportIssue(context, node, issueToReport, lintFix)\n  }\n\n  private fun reportIssue(\n    context: JavaContext,\n    node: UCallExpression,\n    issue: Issue,\n    quickFix: LintFix? = null,\n  ) {\n    context.report(\n      issue,\n      context.getNameLocation(node),\n      issue.getBriefDescription(TextFormat.TEXT),\n      quickFix,\n    )\n\n    check(true)\n  }\n\n  private fun createLintFixTextReplaceString(node: UCallExpression): String {\n    val nodeParent = node.uastParent\n    return if (nodeParent is UQualifiedReferenceExpression) {\n      \"${nodeParent.receiver.sourcePsi?.text}.${node.methodName}\"\n    } else {\n      \"${node.methodName}\"\n    }\n  }\n\n  private fun isUsingGuavaPreconditions(node: UCallExpression): Boolean {\n    return node.resolve()?.containingClass?.qualifiedName == FQN_GUAVA_PRECONDITIONS\n  }\n\n  companion object {\n    private const val FQN_GUAVA_PRECONDITIONS = \"com.google.common.base.Preconditions\"\n    private const val FQN_SLACK_JAVA_PRECONDITIONS = \"slack.commons.JavaPreconditions\"\n\n    private const val METHOD_GUAVA_CHECK_STATE = \"checkState\"\n    private const val METHOD_GUAVA_CHECK_ARGUMENT = \"checkArgument\"\n    private const val METHOD_GUAVA_CHECK_NOT_NULL = \"checkNotNull\"\n\n    private const val METHOD_KOTLIN_CHECK_STATE = \"check\"\n    private const val METHOD_KOTLIN_CHECK_ARGUMENT = \"require\"\n    private const val METHOD_KOTLIN_CHECK_NOT_NULL = \"checkNotNull\"\n\n    private val ISSUE_GUAVA_CHECKS_USED: Issue =\n      Issue.create(\n        \"GuavaChecksUsed\",\n        \"Use Slack's JavaPreconditions instead of Guava's Preconditions checks\",\n        \"\"\"Precondition checks in Java should use Slack's internal `JavaPreconditions.kt` \\\n        instead of Guava's Preconditions.\n      \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        implementation = sourceImplementation<GuavaPreconditionsDetector>(true),\n      )\n\n    private val ISSUE_GUAVA_PRECONDITIONS_USED_IN_KOTLIN: Issue =\n      Issue.create(\n        \"GuavaPreconditionsUsedInKotlin\",\n        \"Kotlin precondition checks should use the Kotlin standard library checks\",\n        \"\"\"All Kotlin classes that require precondition checks should use the \\\n        preconditions checks that are available in the Kotlin standard library in Preconditions.kt.\n        \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        implementation = sourceImplementation<GuavaPreconditionsDetector>(true),\n      )\n\n    val issues: List<Issue> =\n      listOf(ISSUE_GUAVA_CHECKS_USED, ISSUE_GUAVA_PRECONDITIONS_USED_IN_KOTLIN)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/InjectInJavaDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport org.jetbrains.uast.UAnnotated\nimport org.jetbrains.uast.UAnnotation\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UField\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.java.isJava\nimport slack.lint.util.sourceImplementation\n\n/**\n * A simple detector that ensures that `@Inject`, `@Module`, and `@AssistedInject` are not used in\n * Java files in order to properly support Anvil factory generation.\n */\nclass InjectInJavaDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> {\n    return listOf(UClass::class.java, UField::class.java, UMethod::class.java)\n  }\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    // Only applicable to Java files\n    if (!isJava(context.uastFile?.lang)) return null\n\n    return object : UElementHandler() {\n      private fun checkNode(node: UAnnotated) {\n        node.findInjectionAnnotation()?.let { annotation ->\n          context.report(\n            ISSUE,\n            context.getNameLocation(annotation),\n            ISSUE.getBriefDescription(TextFormat.TEXT),\n            quickfixData = null,\n          )\n        }\n      }\n\n      override fun visitClass(node: UClass) = checkNode(node)\n\n      override fun visitMethod(node: UMethod) = checkNode(node)\n\n      override fun visitField(node: UField) = checkNode(node)\n    }\n  }\n\n  companion object {\n    private const val FQCN_INJECT = \"javax.inject.Inject\"\n    private const val FQCN_MODULE = \"dagger.Module\"\n    private const val FQCN_ASSISTED_INJECT = \"dagger.assisted.AssistedInject\"\n    private const val FQCN_ASSISTED_FACTORY = \"dagger.assisted.AssistedFactory\"\n\n    val ISSUE: Issue =\n      Issue.create(\n        \"InjectInJava\",\n        \"Only Kotlin classes should be injected in order for Anvil to work.\",\n        \"\"\"\n        Only Kotlin classes should be injected in order for Anvil to work. If you \\\n        cannot easily convert this to Kotlin, consider manually providing it via a Kotlin \\\n        `@Module`-annotated object.\n      \"\"\",\n        Category.CORRECTNESS,\n        9,\n        Severity.ERROR,\n        sourceImplementation<InjectInJavaDetector>(),\n      )\n\n    private val ANNOTATIONS =\n      setOf(FQCN_INJECT, FQCN_ASSISTED_INJECT, FQCN_MODULE, FQCN_ASSISTED_FACTORY)\n\n    private fun UAnnotated.findInjectionAnnotation(): UAnnotation? {\n      for (annotation in ANNOTATIONS) {\n        return findAnnotation(annotation) ?: continue\n      }\n      return null\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/JavaOnlyDetector.kt",
    "content": "// Copyright (C) 2020 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.UastLintUtils\nimport com.intellij.psi.PsiClass\nimport com.intellij.psi.PsiClassType\nimport org.jetbrains.uast.UAnnotation\nimport org.jetbrains.uast.UAnonymousClass\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UCallableReferenceExpression\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UExpression\nimport org.jetbrains.uast.ULambdaExpression\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.UReturnExpression\nimport org.jetbrains.uast.getContainingUClass\nimport org.jetbrains.uast.getContainingUMethod\nimport org.jetbrains.uast.kotlin.isKotlin\nimport org.jetbrains.uast.toUElementOfType\nimport slack.lint.util.sourceImplementation\n\n/**\n * Logic adapted from the analogous KotlinOnlyChecker in Error-Prone.\n *\n * Consuming repos should create and use `@KotlinOnly` and `@JavaOnly` annotations from the\n * `slack-lint-annotations` artifact. We would normally like to consume these via properties\n * defining them, but lint APIs only allow reading APIs from project-local gradle.properties and not\n * root properties files.\n *\n * Copied recipe from https://github.com/uber/lint-checks\n */\nclass JavaOnlyDetector : Detector(), SourceCodeScanner {\n  companion object {\n    private const val KOTLIN_ONLY = \"slack.lint.annotations.KotlinOnly\"\n    private const val JAVA_ONLY = \"slack.lint.annotations.JavaOnly\"\n    private const val ISSUE_ID = \"JavaOnlyDetector\"\n    private const val MESSAGE_LINT_ERROR_TITLE = \"Using @JavaOnly elements in Kotlin code.\"\n    private const val MESSAGE_LINT_ERROR_EXPLANATION = \"This should not be called from Kotlin code\"\n    @JvmField\n    val ISSUE =\n      Issue.create(\n        ISSUE_ID,\n        MESSAGE_LINT_ERROR_TITLE,\n        MESSAGE_LINT_ERROR_EXPLANATION,\n        Category.INTEROPERABILITY_KOTLIN,\n        6,\n        Severity.ERROR,\n        sourceImplementation<JavaOnlyDetector>(),\n      )\n\n    private fun anonymousTypeString(psiClass: PsiClass, type: String): String {\n      return \"Cannot create $type instances of @JavaOnly-annotated type ${UastLintUtils.getClassName(psiClass)} (in ${psiClass.containingFile.name}) \" +\n        \"in Kotlin. Make a concrete class instead.\"\n    }\n  }\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    // We only run this on Kotlin files, the ErrorProne analogue handles Java files. Can revisit\n    // if we get lint in the IDE or otherwise unify\n    if (!isKotlin(context.uastFile?.lang)) return null\n\n    return object : UElementHandler() {\n      override fun visitClass(node: UClass) {\n        val hasJavaOnly = context.evaluator.getAnnotation(node, JAVA_ONLY) != null\n        val hasKotlinOnly = context.evaluator.getAnnotation(node, KOTLIN_ONLY) != null\n        if (hasJavaOnly && hasKotlinOnly) {\n          context.report(\n            ISSUE,\n            context.getLocation(node.sourcePsi!!),\n            \"Cannot annotate types with both `@KotlinOnly` and `@JavaOnly`\",\n          )\n          return\n        }\n        if (hasJavaOnly || hasKotlinOnly) {\n          return\n        }\n        if (node is UAnonymousClass) {\n          if (node.uastParent.isReturnExpression() && node.isEnclosedInJavaOnlyMethod()) {\n            return\n          }\n          node.baseClassType.resolve()?.let { psiClass ->\n            context.evaluator.getAnnotation(psiClass, JAVA_ONLY)?.run {\n              val message = anonymousTypeString(psiClass, \"anonymous\")\n              context.report(ISSUE, context.getLocation(node.sourcePsi!!), message)\n            }\n          }\n          return\n        }\n        val reportData =\n          checkMissingSubclass(node, KOTLIN_ONLY, \"KotlinOnly\")\n            ?: checkMissingSubclass(node, JAVA_ONLY, \"JavaOnly\")\n            ?: return\n        context.report(\n          ISSUE,\n          context.getLocation(node.sourcePsi!!),\n          reportData.first,\n          reportData.second,\n        )\n      }\n\n      private fun checkMissingSubclass(\n        node: UClass,\n        targetAnnotation: String,\n        targetAnnotationSimpleName: String,\n      ): Pair<String, LintFix>? {\n        return listOfNotNull(node.javaPsi.superClass, *node.interfaces)\n          .mapNotNull { psiClass ->\n            context.evaluator.getAnnotation(psiClass, targetAnnotation)?.run {\n              val message =\n                \"Type subclasses/implements ${UastLintUtils.getClassName(psiClass)} in ${psiClass.containingFile.name} which is annotated @$targetAnnotationSimpleName, it should also be annotated.\"\n              val source = node.text\n              return@mapNotNull message to\n                fix()\n                  .replace()\n                  .name(\"Add @$targetAnnotationSimpleName\")\n                  .range(context.getLocation(node.sourcePsi!!))\n                  .shortenNames()\n                  .text(source)\n                  .with(\"@$targetAnnotation $source\")\n                  .autoFix()\n                  .build()\n            }\n          }\n          .firstOrNull()\n      }\n\n      override fun visitLambdaExpression(node: ULambdaExpression) {\n        if (node.isReturnExpression() && node.isEnclosedInJavaOnlyMethod()) {\n          return\n        }\n        node.functionalInterfaceType?.let { type ->\n          if (type is PsiClassType) {\n            type.resolve()?.let { psiClass ->\n              context.evaluator.getAnnotation(psiClass, JAVA_ONLY)?.let {\n                val message = anonymousTypeString(psiClass, \"lambda\")\n                context.report(ISSUE, context.getLocation(node.sourcePsi!!), message)\n                return\n              }\n              val functionalMethod = psiClass.methods.firstOrNull() ?: return\n              functionalMethod.toUElementOfType<UMethod>()?.isAnnotationPresent()?.let {\n                node.report(it, \"expressed as a lambda in Kotlin\")\n              }\n            }\n          }\n        }\n      }\n\n      override fun visitMethod(node: UMethod) {\n        val hasJavaOnly = context.evaluator.getAnnotation(node, JAVA_ONLY) != null\n        val hasKotlinOnly = context.evaluator.getAnnotation(node, KOTLIN_ONLY) != null\n        if (hasJavaOnly && hasKotlinOnly) {\n          context.report(\n            ISSUE,\n            context.getLocation(node.sourcePsi!!),\n            \"Cannot annotate functions with both `@KotlinOnly` and `@JavaOnly`\",\n          )\n          return\n        }\n        if (hasJavaOnly || hasKotlinOnly) {\n          return\n        }\n        val reportData =\n          checkMissingOverride(node, KOTLIN_ONLY, \"KotlinOnly\")\n            ?: checkMissingOverride(node, JAVA_ONLY, \"JavaOnly\")\n            ?: return\n        context.report(ISSUE, context.getLocation(node), reportData.first, reportData.second)\n      }\n\n      private fun checkMissingOverride(\n        node: UMethod,\n        targetAnnotation: String,\n        targetAnnotationSimpleName: String,\n      ): Pair<String, LintFix>? {\n        return context.evaluator.getSuperMethod(node)?.let { method ->\n          context.evaluator.getAnnotation(method, targetAnnotation)?.run {\n            val message =\n              \"Function overrides ${method.name} in ${\n                UastLintUtils.getClassName(\n                  method.containingClass!!\n                )\n              } which is annotated @$targetAnnotationSimpleName, it should also be annotated.\"\n            val modifier = node.modifierList.children.joinToString(separator = \" \") { it.text }\n            return@let message to\n              fix()\n                .replace()\n                .name(\"Add @$targetAnnotationSimpleName\")\n                .range(context.getLocation(node))\n                .shortenNames()\n                .text(modifier)\n                .with(\"@$targetAnnotation $modifier\")\n                .autoFix()\n                .build()\n          }\n        }\n      }\n\n      override fun visitCallExpression(node: UCallExpression) {\n        node.resolve().toUElementOfType<UMethod>()?.isAnnotationPresent()?.let { node.report(it) }\n      }\n\n      override fun visitCallableReferenceExpression(node: UCallableReferenceExpression) {\n        node.resolve().toUElementOfType<UMethod>()?.isAnnotationPresent()?.let { node.report(it) }\n      }\n\n      private fun UExpression.report(\n        javaOnlyMessage: String?,\n        callString: String = \"called from Kotlin\",\n      ) {\n        val message = StringBuilder(\"This method should not be $callString\")\n        if (javaOnlyMessage.isNullOrBlank()) {\n          message.append(\", see its documentation for details.\")\n        } else {\n          message.append(\": $javaOnlyMessage\")\n        }\n        context.report(ISSUE, context.getLocation(this), message.toString())\n      }\n\n      private fun UElement?.isReturnExpression(): Boolean =\n        this != null && uastParent is UReturnExpression\n\n      private fun UElement.isEnclosedInJavaOnlyMethod(): Boolean {\n        return getContainingUMethod()?.isAnnotationPresent() != null\n      }\n\n      private fun UMethod.isAnnotationPresent(): String? {\n        findAnnotation(JAVA_ONLY)?.let {\n          return it.extractValue()\n        }\n        getContainingUClass()?.findAnnotation(JAVA_ONLY)?.let {\n          return it.extractValue()\n        }\n        context.evaluator.getPackage(this)?.let { pkg ->\n          context.evaluator.getAnnotation(pkg, KOTLIN_ONLY)?.let {\n            return it.extractValue()\n          }\n        }\n        return null\n      }\n\n      private fun UAnnotation.extractValue(): String {\n        return UastLintUtils.getAnnotationStringValue(this, \"reason\").orEmpty()\n      }\n    }\n  }\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> {\n    return listOf(\n      UMethod::class.java,\n      UCallExpression::class.java,\n      UCallableReferenceExpression::class.java,\n      ULambdaExpression::class.java,\n      UClass::class.java,\n    )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/JsonInflaterMoshiCompatibilityDetector.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.intellij.psi.PsiCapturedWildcardType\nimport com.intellij.psi.PsiClass\nimport com.intellij.psi.PsiClassType\nimport com.intellij.psi.PsiModifier\nimport com.intellij.psi.PsiPrimitiveType\nimport com.intellij.psi.PsiType\nimport com.intellij.psi.PsiWildcardType\nimport com.intellij.psi.util.PsiTypesUtil\nimport org.jetbrains.kotlin.asJava.classes.KtLightClass\nimport org.jetbrains.kotlin.lexer.KtTokens\nimport org.jetbrains.uast.UCallExpression\nimport slack.lint.moshi.MoshiLintUtil.hasMoshiAnnotation\nimport slack.lint.util.sourceImplementation\n\n/**\n * A detector that checks if a type passed into the JsonInflater.inflate/deflate methods follows\n * Moshi's requirements for serialization/deserialization.\n *\n * `JsonInflater` is a JSON serialization indirection we have internally at Slack.\n */\nclass JsonInflaterMoshiCompatibilityDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitCallExpression(node: UCallExpression) {\n        val method = node.resolve() ?: return\n\n        if (isJsonInflaterInflateOrDeflate(node)) {\n          when (method.name) {\n            \"inflate\" -> validateInflateReturnType(context, node)\n            \"deflate\" -> validateDeflateArguments(context, node)\n          }\n        }\n      }\n    }\n  }\n\n  private fun isJsonInflaterInflateOrDeflate(node: UCallExpression): Boolean {\n    // Get the method being called\n    val method = node.resolve() ?: return false\n\n    // Check if it's from JsonInflater class\n    val containingClass = method.containingClass?.qualifiedName ?: return false\n    if (containingClass != FQCN_JSON_INFLATER) return false\n\n    // Check if it's an inflate or deflate method\n    return method.name == \"inflate\" || method.name == \"deflate\"\n  }\n\n  private fun validateInflateReturnType(context: JavaContext, node: UCallExpression) {\n    // Get the return type of the inflate call\n    val returnType = node.getExpressionType() ?: return\n\n    // Skip checking primitive types and String\n    if (isJsonPrimitive(returnType)) return\n\n    // Validate if the class is Moshi-compatible\n    validateClassForMoshiCompatibility(context, node, returnType)\n  }\n\n  private fun isJsonPrimitive(type: PsiType): Boolean {\n    val isString =\n      if (type is PsiClassType) {\n        type.resolve()?.qualifiedName == FQCN_JAVA_STRING\n      } else {\n        false\n      }\n    return type is PsiPrimitiveType || isString\n  }\n\n  private fun validateDeflateArguments(context: JavaContext, node: UCallExpression) {\n    val method = node.resolve() ?: return\n\n    val parameters = method.parameterList.parameters\n    val valueParamIndex = parameters.indexOfFirst { it.name == \"value\" }\n    if (valueParamIndex == -1) return\n\n    val valueArg = node.getArgumentForParameter(valueParamIndex) ?: return\n    val valueArgType = valueArg.getExpressionType() ?: return\n\n    validateClassForMoshiCompatibility(context, node, valueArgType)\n  }\n\n  private fun validateClassForMoshiCompatibility(\n    context: JavaContext,\n    node: UCallExpression,\n    typeToValidate: PsiType,\n  ) {\n    val modelClasses = extractModelClasses(typeToValidate)\n\n    if (modelClasses.any { !isMoshiCompatible(it) }) {\n      context.report(\n        issue = ISSUE,\n        location = context.getLocation(node),\n        message = ISSUE.getBriefDescription(TextFormat.TEXT),\n      )\n    }\n  }\n\n  private fun extractModelClasses(type: PsiType): List<PsiClass> {\n    val result = mutableListOf<PsiClass>()\n\n    fun processType(t: PsiType) {\n      val unwrapped =\n        when (t) {\n          is PsiWildcardType -> t.bound ?: return\n          is PsiCapturedWildcardType -> t.wildcard.bound ?: return\n          else -> t\n        }\n\n      val psiClass = PsiTypesUtil.getPsiClass(unwrapped)\n      if (psiClass != null) {\n        result.add(psiClass)\n      }\n\n      if (unwrapped is PsiClassType) {\n        for (param in unwrapped.parameters) {\n          processType(param)\n        }\n      }\n    }\n\n    processType(type)\n    return result\n  }\n\n  private fun isMoshiCompatible(psiClass: PsiClass): Boolean {\n    if (isPrimitiveType(psiClass)) return true\n\n    if (isCollectionType(psiClass)) return true\n\n    if (isAbstractOrNonPublicClass(psiClass)) return false\n\n    if (psiClass.isInterface && !isSealedInterface(psiClass)) return false\n\n    return psiClass.hasMoshiAnnotation()\n  }\n\n  private fun isCollectionType(psiClass: PsiClass): Boolean {\n    val qualifiedName = psiClass.qualifiedName ?: return false\n    return qualifiedName in listOf(FQCN_LIST, FQCN_SET, FQCN_MAP, FQCN_COLLECTION)\n  }\n\n  private fun isPrimitiveType(psiClass: PsiClass): Boolean {\n    val qualifiedName = psiClass.qualifiedName ?: return false\n    return qualifiedName in\n      listOf(\n        FQCN_JAVA_STRING,\n        FQCN_JAVA_BOOLEAN,\n        FQCN_JAVA_BYTE,\n        FQCN_JAVA_CHARACTER,\n        FQCN_JAVA_SHORT,\n        FQCN_JAVA_INTEGER,\n        FQCN_JAVA_LONG,\n        FQCN_JAVA_FLOAT,\n        FQCN_JAVA_DOUBLE,\n      )\n  }\n\n  private fun isAbstractOrNonPublicClass(psiClass: PsiClass): Boolean {\n    if (psiClass.isInterface) return false\n\n    // We return false for sealed classes here even though they are technically considered abstract\n    // by PsiClass. From a Moshi perspective, sealed classes can be compatible, while abstract\n    // classes cannot.\n    if (isSealedClass(psiClass)) return false\n\n    return (psiClass.hasModifierProperty(PsiModifier.ABSTRACT) ||\n      !psiClass.hasModifierProperty(PsiModifier.PUBLIC))\n  }\n\n  private fun isSealedClass(psiClass: PsiClass): Boolean {\n    if (psiClass.isInterface) return false\n\n    // For Kotlin classes, check using Kotlin PSI\n    if (psiClass is KtLightClass) {\n      val ktClass = psiClass.kotlinOrigin\n      return ktClass?.hasModifier(KtTokens.SEALED_KEYWORD) == true\n    }\n\n    // Fallback for Java classes (Java doesn't have sealed classes in older versions)\n    return psiClass.hasModifierProperty(PsiModifier.SEALED)\n  }\n\n  private fun isSealedInterface(psiClass: PsiClass): Boolean {\n    if (!psiClass.isInterface) return false\n\n    // For Kotlin classes, check using Kotlin PSI\n    if (psiClass is KtLightClass) {\n      val ktClass = psiClass.kotlinOrigin\n      return ktClass?.hasModifier(KtTokens.SEALED_KEYWORD) == true\n    }\n\n    // Fallback for Java classes\n    return psiClass.hasModifierProperty(PsiModifier.SEALED)\n  }\n\n  companion object {\n    // Fully qualified class names for relevant annotations and types\n    private const val FQCN_JSON_INFLATER = \"slack.commons.json.JsonInflater\"\n    private const val FQCN_JAVA_STRING = \"java.lang.String\"\n    private const val FQCN_JAVA_BOOLEAN = \"java.lang.Boolean\"\n    private const val FQCN_JAVA_BYTE = \"java.lang.Byte\"\n    private const val FQCN_JAVA_CHARACTER = \"java.lang.Character\"\n    private const val FQCN_JAVA_SHORT = \"java.lang.Short\"\n    private const val FQCN_JAVA_INTEGER = \"java.lang.Integer\"\n    private const val FQCN_JAVA_LONG = \"java.lang.Long\"\n    private const val FQCN_JAVA_FLOAT = \"java.lang.Float\"\n    private const val FQCN_JAVA_DOUBLE = \"java.lang.Double\"\n    private const val FQCN_LIST = \"java.util.List\"\n    private const val FQCN_SET = \"java.util.Set\"\n    private const val FQCN_MAP = \"java.util.Map\"\n    private const val FQCN_COLLECTION = \"java.util.Collection\"\n\n    val ISSUE =\n      Issue.create(\n        id = \"JsonInflaterMoshiIncompatibleType\",\n        \"Using JsonInflater.inflate/deflate with a Moshi-incompatible type.\",\n        \"\"\"\n          Classes used with JsonInflater.inflate/deflate must be annotated with @JsonClass or @AdaptedBy to make it \\\n          compatible with Moshi. Additionally, it cannot be an abstract class or an interface.\n        \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        implementation = sourceImplementation<JsonInflaterMoshiCompatibilityDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/MainScopeUsageDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.intellij.psi.PsiClass\nimport com.intellij.psi.PsiClassType\nimport java.util.EnumSet\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UCallableReferenceExpression\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.kotlin.isKotlin\n\n/**\n * This is a [Detector] for detecting direct usages of Kotlin coroutines'\n * [kotlinx.coroutines.MainScope] helper function, as we want folks to use our\n * `slack.foundation.coroutines.android.MainScope` alternative.\n */\nclass MainScopeUsageDetector : Detector(), SourceCodeScanner {\n\n  companion object {\n    private val SCOPES =\n      Implementation(MainScopeUsageDetector::class.java, EnumSet.of(Scope.JAVA_FILE))\n\n    val ISSUE: Issue =\n      Issue.create(\n        \"MainScopeUsage\",\n        \"Use slack.foundation.coroutines.android.MainScope.\",\n        \"\"\"\n        Prefer using Slack's internal `MainScope` function, which supports `SlackDispatchers` and uses \\\n        Dispatchers.Main.immediate under the hood.\n      \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        SCOPES,\n      )\n\n    private const val COROUTINE_SCOPE_CLASS = \"kotlinx.coroutines.CoroutineScopeKt\"\n    private const val MAIN_SCOPE_FUNCTION = \"MainScope\"\n  }\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> =\n    listOf(UCallExpression::class.java, UCallableReferenceExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    // Only applicable on Kotlin files\n    if (!isKotlin(context.uastFile?.lang)) return null\n\n    fun report(node: UElement) {\n      context.report(\n        ISSUE,\n        context.getLocation(node),\n        ISSUE.getBriefDescription(TextFormat.TEXT),\n        LintFix.create()\n          .replace()\n          .name(\"Use slack.foundation.coroutines.android.MainScope\")\n          .text(\"MainScope(\")\n          .with(\"slack.foundation.coroutines.android.MainScope(\")\n          .autoFix()\n          .build(),\n      )\n    }\n\n    fun String?.isMainScope(): Boolean {\n      return this == MAIN_SCOPE_FUNCTION\n    }\n\n    fun PsiClass?.isCoroutineScopeClass(): Boolean {\n      if (this == null) return false\n      return qualifiedName == COROUTINE_SCOPE_CLASS\n    }\n\n    return object : UElementHandler() {\n      override fun visitCallExpression(node: UCallExpression) {\n        if (node.methodName.isMainScope()) {\n          val resolved = node.resolve() ?: return\n          if (resolved.containingClass.isCoroutineScopeClass()) {\n            report(node)\n          }\n        }\n      }\n\n      override fun visitCallableReferenceExpression(node: UCallableReferenceExpression) {\n        if (node.callableName.isMainScope()) {\n          val qualifierType = node.qualifierType ?: return\n          if (qualifierType is PsiClassType && qualifierType.resolve().isCoroutineScopeClass())\n            report(node)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/MoshiUsageDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.android.tools.lint.detector.api.getUMethod\nimport com.android.tools.lint.detector.api.isKotlin\nimport com.intellij.psi.PsiArrayType\nimport com.intellij.psi.PsiClassType\nimport com.intellij.psi.PsiElement\nimport com.intellij.psi.PsiNamedElement\nimport com.intellij.psi.PsiPrimitiveType\nimport com.intellij.psi.PsiType\nimport com.intellij.psi.PsiTypeParameter\nimport com.intellij.psi.util.InheritanceUtil.isInheritor\nimport org.jetbrains.kotlin.lexer.KtTokens\nimport org.jetbrains.kotlin.psi.KtClass\nimport org.jetbrains.kotlin.psi.KtCollectionLiteralExpression\nimport org.jetbrains.kotlin.psi.KtExpression\nimport org.jetbrains.kotlin.psi.KtModifierListOwner\nimport org.jetbrains.kotlin.psi.KtParameter\nimport org.jetbrains.kotlin.psi.KtPrimaryConstructor\nimport org.jetbrains.kotlin.psi.KtQualifiedExpression\nimport org.jetbrains.kotlin.psi.KtReferenceExpression\nimport org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter\nimport org.jetbrains.kotlin.psi.psiUtil.visibilityModifier\nimport org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault\nimport org.jetbrains.uast.UAnnotated\nimport org.jetbrains.uast.UAnnotation\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UEnumConstant\nimport org.jetbrains.uast.UExpression\nimport org.jetbrains.uast.ULiteralExpression\nimport org.jetbrains.uast.UParameter\nimport org.jetbrains.uast.UReferenceExpression\nimport org.jetbrains.uast.getContainingUFile\nimport org.jetbrains.uast.getUastParentOfType\nimport org.jetbrains.uast.kotlin.KotlinUAnnotation\nimport org.jetbrains.uast.kotlin.KotlinUClassLiteralExpression\nimport org.jetbrains.uast.toUElement\nimport org.jetbrains.uast.toUElementOfType\nimport slack.lint.moshi.MoshiLintUtil.hasMoshiAnnotation\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.isBoxedPrimitive\nimport slack.lint.util.isInnerClass\nimport slack.lint.util.isObjectOrAny\nimport slack.lint.util.isPlatformType\nimport slack.lint.util.isString\nimport slack.lint.util.removeNode\nimport slack.lint.util.snakeToCamel\nimport slack.lint.util.sourceImplementation\nimport slack.lint.util.toScreamingSnakeCase\nimport slack.lint.util.unwrapSimpleNameReferenceExpression\n\n/** A detector for a number of issues related to Moshi usage. */\n// TODO could we detect call expressions to JsonInflater/Gson/Moshi and check if\n//  the type going in is a moshi class and meets these requirements?\nclass MoshiUsageDetector : Detector(), SourceCodeScanner {\n  override fun getApplicableUastTypes() = listOf(UClass::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    val slackEvaluator = MetadataJavaEvaluator(context.file.name, context.evaluator)\n    return object : UElementHandler() {\n      override fun visitClass(node: UClass) {\n        // Enums get checked in both languages because it's easy enough\n        if (node.isEnum) {\n          checkEnum(context, node)\n          return\n        }\n\n        if (node.isAnnotationType) {\n          checkJsonQualifierAnnotation(context, node)\n        }\n\n        context.uastFile?.lang?.let { language -> if (!isKotlin(language)) return }\n\n        val adaptedByAnnotation = node.findAnnotation(FQCN_ADAPTED_BY)\n        val jsonClassAnnotation = node.findAnnotation(FQCN_JSON_CLASS)\n        if (adaptedByAnnotation != null) {\n          if (jsonClassAnnotation != null) {\n            // Report both\n            context.report(\n              ISSUE_DOUBLE_CLASS_ANNOTATION,\n              context.getNameLocation(adaptedByAnnotation),\n              ISSUE_DOUBLE_CLASS_ANNOTATION.getBriefDescription(TextFormat.TEXT),\n              fix().removeNode(context, adaptedByAnnotation.sourcePsi!!),\n            )\n            context.report(\n              ISSUE_DOUBLE_CLASS_ANNOTATION,\n              context.getNameLocation(jsonClassAnnotation),\n              ISSUE_DOUBLE_CLASS_ANNOTATION.getBriefDescription(TextFormat.TEXT),\n              fix().removeNode(context, jsonClassAnnotation.sourcePsi!!),\n            )\n          }\n\n          validateAdaptedByAnnotation(context, slackEvaluator, adaptedByAnnotation)\n          return\n        }\n\n        checkSealedClass(node)\n\n        if (jsonClassAnnotation == null) return\n\n        val ktModifierListOwner = node.sourcePsi as KtModifierListOwner\n        val visibility = ktModifierListOwner.visibilityModifierTypeOrDefault()\n        if (visibility !in REQUIRED_VISIBILITIES) {\n          val visibilityElement = ktModifierListOwner.visibilityModifier()\n          val location = context.getLocation(visibilityElement!!)\n          context.report(\n            ISSUE_VISIBILITY,\n            location,\n            ISSUE_VISIBILITY.getBriefDescription(TextFormat.TEXT),\n            quickfixData =\n              fix()\n                .replace()\n                .name(\"Make 'internal'\")\n                .range(location)\n                .shortenNames()\n                .text(visibility.value)\n                .with(\"internal\")\n                .autoFix()\n                .build(),\n          )\n        }\n\n        // Check that generateAdapter is false\n        var usesCustomGenerator = false\n\n        // Check the `JsonClass.generator` property first\n        // If generator is an empty string (the default), then it's a class.\n        // If it were something else, a generator is claiming to generate something for it\n        // and we choose to ignore it in that case.\n        // This can sometimes come back as the default empty String and sometimes as null if not\n        // defined\n        val generatorExpression = jsonClassAnnotation.findDeclaredAttributeValue(\"generator\")\n        val generator = generatorExpression?.evaluate() as? String\n        if (generator != null) {\n          if (generator.isBlank()) {\n            context.report(\n              ISSUE_BLANK_GENERATOR,\n              context.getLocation(generatorExpression),\n              ISSUE_BLANK_GENERATOR.getBriefDescription(TextFormat.TEXT),\n              quickfixData = null,\n            )\n          } else {\n            usesCustomGenerator = true\n            if (generator.startsWith(\"sealed:\")) {\n              val typeLabel = generator.removePrefix(\"sealed:\")\n              if (typeLabel.isBlank()) {\n                context.report(\n                  ISSUE_BLANK_TYPE_LABEL,\n                  context.getLocation(generatorExpression),\n                  ISSUE_BLANK_TYPE_LABEL.getBriefDescription(TextFormat.TEXT),\n                  quickfixData = null,\n                )\n              }\n              if (!slackEvaluator.isSealed(node)) {\n                context.report(\n                  ISSUE_SEALED_MUST_BE_SEALED,\n                  context.getNameLocation(node),\n                  ISSUE_SEALED_MUST_BE_SEALED.getBriefDescription(TextFormat.TEXT),\n                  quickfixData = null,\n                )\n              }\n            }\n          }\n        }\n\n        val generateAdapterExpression = jsonClassAnnotation.findAttributeValue(\"generateAdapter\")\n        val generateAdapter = generateAdapterExpression?.evaluate() as? Boolean? ?: false\n        val isData = slackEvaluator.isData(node)\n        if (!generateAdapter) {\n          // If it's a data class always report these because it's probably user error\n          if (isData) {\n            context.report(\n              ISSUE_GENERATE_ADAPTER_SHOULD_BE_TRUE,\n              context.getLocation(generateAdapterExpression as UExpression),\n              ISSUE_GENERATE_ADAPTER_SHOULD_BE_TRUE.getBriefDescription(TextFormat.TEXT),\n              fix()\n                .replace()\n                .name(\"Set to true\")\n                .text(generateAdapterExpression.asSourceString())\n                .with(\"true\")\n                .autoFix()\n                .build(),\n            )\n          }\n\n          // It's ok if we're just doing generateAdapter = false as these might be for R8 reasons\n          return\n        }\n\n        // If they're using a custom generator, peace of out this\n        if (usesCustomGenerator) return\n\n        val isUnsupportedType =\n          slackEvaluator.isObject(node) ||\n            node.isAnnotationType ||\n            node.isInterface ||\n            node.isInnerClass(slackEvaluator) ||\n            slackEvaluator.isAbstract(node)\n\n        if (isUnsupportedType) {\n          if (slackEvaluator.isObject(node)) {\n            // Kotlin objects are ok in certain cases, so we give a more specific error message\n            context.report(\n              ISSUE_OBJECT,\n              context.getNameLocation(jsonClassAnnotation),\n              ISSUE_OBJECT.getBriefDescription(TextFormat.TEXT),\n              fix().removeNode(context, jsonClassAnnotation.sourcePsi!!),\n            )\n          } else {\n            context.report(\n              ISSUE_UNSUPPORTED_TYPE,\n              context.getNameLocation(jsonClassAnnotation),\n              ISSUE_UNSUPPORTED_TYPE.getBriefDescription(TextFormat.TEXT),\n              fix().removeNode(context, jsonClassAnnotation.sourcePsi!!),\n            )\n          }\n          return\n        }\n\n        if (!isData) {\n          // These should be data classes unless there's a very specific reason not to\n          context.report(\n            ISSUE_USE_DATA,\n            context.getNameLocation(node),\n            ISSUE_USE_DATA.getBriefDescription(TextFormat.TEXT),\n          )\n        }\n\n        // Visit primary constructor properties\n        val primaryConstructor =\n          node.constructors\n            .asSequence()\n            .mapNotNull { it.getUMethod() }\n            .firstOrNull { it.sourcePsi is KtPrimaryConstructor }\n\n        if (primaryConstructor == null) {\n          context.report(\n            ISSUE_MISSING_PRIMARY,\n            context.getNameLocation(node),\n            ISSUE_MISSING_PRIMARY.getBriefDescription(TextFormat.TEXT),\n            quickfixData = null,\n          )\n          return\n        }\n\n        val pConstructorPsi = primaryConstructor.sourcePsi as KtPrimaryConstructor\n        val constructorVisibility = pConstructorPsi.visibilityModifierTypeOrDefault()\n        if (constructorVisibility !in REQUIRED_VISIBILITIES) {\n          val visibilityElement = pConstructorPsi.visibilityModifier()!!\n          val location = context.getLocation(visibilityElement)\n          context.report(\n            ISSUE_PRIVATE_CONSTRUCTOR,\n            location,\n            ISSUE_PRIVATE_CONSTRUCTOR.getBriefDescription(TextFormat.TEXT),\n            quickfixData =\n              fix()\n                .replace()\n                .name(\"Make constructor 'internal'\")\n                .range(location)\n                .shortenNames()\n                .text(constructorVisibility.value)\n                .with(\"internal\")\n                .autoFix()\n                .build(),\n          )\n        }\n\n        val jsonNames = mutableMapOf<String, PsiNamedElement>()\n        for (parameter in primaryConstructor.uastParameters) {\n          val sourcePsi = parameter.sourcePsi\n          val defaultValueExpression =\n            if (sourcePsi is KtParameter) {\n              sourcePsi.defaultValue\n            } else {\n              null\n            }\n          val hasDefaultValue = defaultValueExpression != null\n          if (\n            parameter.uAnnotations.any { it.qualifiedName == \"kotlin.jvm.Transient\" } &&\n              !hasDefaultValue\n          ) {\n            ISSUE_TRANSIENT_NEEDS_INIT\n            context.report(\n              ISSUE_TRANSIENT_NEEDS_INIT,\n              context.getLocation(parameter as UElement),\n              ISSUE_TRANSIENT_NEEDS_INIT.getBriefDescription(TextFormat.TEXT),\n              quickfixData = null,\n            )\n          }\n          if (sourcePsi is KtParameter && sourcePsi.isPropertyParameter()) {\n            if (sourcePsi.isMutable) {\n              val location = context.getLocation(sourcePsi.valOrVarKeyword!!)\n              context.report(\n                ISSUE_VAR_PROPERTY,\n                location,\n                ISSUE_VAR_PROPERTY.getBriefDescription(TextFormat.TEXT),\n                quickfixData =\n                  fix()\n                    .replace()\n                    .name(\"Make ${parameter.name} 'val'\")\n                    .range(location)\n                    .shortenNames()\n                    .text(\"var\")\n                    .with(\"val\")\n                    .autoFix()\n                    .build(),\n              )\n            }\n            val paramVisibility = sourcePsi.visibilityModifierTypeOrDefault()\n            if (paramVisibility !in REQUIRED_VISIBILITIES) {\n              val visibilityElement = sourcePsi.visibilityModifier()!!\n              val location = context.getLocation(visibilityElement)\n              context.report(\n                ISSUE_PRIVATE_PARAMETER,\n                location,\n                ISSUE_PRIVATE_PARAMETER.getBriefDescription(TextFormat.TEXT),\n                quickfixData =\n                  fix()\n                    .replace()\n                    .name(\"Make ${parameter.name} 'internal'\")\n                    .range(location)\n                    .shortenNames()\n                    .text(paramVisibility.value)\n                    .with(\"internal\")\n                    .autoFix()\n                    .build(),\n              )\n            }\n\n            val propertyAdaptedByAnnotation = parameter.findAnnotation(FQCN_ADAPTED_BY)\n            if (propertyAdaptedByAnnotation != null) {\n              validateAdaptedByAnnotation(context, slackEvaluator, propertyAdaptedByAnnotation)\n            }\n\n            val shouldCheckPropertyType =\n              propertyAdaptedByAnnotation == null &&\n                // If any JsonQualifier annotations are present, defer to whatever adapter looks at\n                // them\n                parameter.uAnnotations.none {\n                  it.resolve()?.hasAnnotation(FQCN_JSON_QUALIFIER) == true\n                }\n\n            if (shouldCheckPropertyType) {\n              checkMoshiType(\n                context,\n                slackEvaluator,\n                parameter.type,\n                parameter,\n                parameter.typeReference!!,\n                defaultValueExpression,\n                nestedGenericCheck = false,\n              )\n            }\n\n            // Note: this is a sort of round-about way to get all annotations because just calling\n            // UAnnotatedElement.uAnnotations will only return one annotation with a matching name\n            // when we want to check for possible duplicates here.\n            val jsonAnnotations =\n              (parameter.sourcePsi as KtParameter).annotationEntries.mapNotNull { annotationEntry ->\n                (annotationEntry.toUElement() as KotlinUAnnotation).takeIf {\n                  it.qualifiedName == FQCN_JSON\n                }\n              }\n            var jsonAnnotationToValidate: UAnnotation? = null\n            if (jsonAnnotations.isNotEmpty()) {\n              if (jsonAnnotations.size != 1) {\n                // If we have multiple @Json annotations, likely one is correct (i.e. @Json) and the\n                // the others are someone guessing to use a site target but not realizing it's not\n                // necessary. So, we suggest removing any with site targets.\n\n                // If, for some reason, _all_ of them are using site targets, then keep one and let\n                // a later detector suggest removing the site target.\n                val annotationToKeep =\n                  jsonAnnotations.find { it.sourcePsi.useSiteTarget == null } ?: jsonAnnotations[0]\n                for (annotation in jsonAnnotations) {\n                  if (annotation == annotationToKeep) continue\n                  // Suggest removing the entire extra annotation\n                  context.report(\n                    ISSUE_JSON_SITE_TARGET,\n                    context.getLocation(annotation),\n                    ISSUE_JSON_SITE_TARGET.getBriefDescription(TextFormat.TEXT),\n                    quickfixData = fix().removeNode(context, annotation.sourcePsi),\n                  )\n                }\n              } else {\n                // Check for site targets, which are redundant\n                val jsonAnnotation = jsonAnnotations[0]\n                jsonAnnotation.sourcePsi.useSiteTarget?.let { siteTarget ->\n                  context.report(\n                    ISSUE_JSON_SITE_TARGET,\n                    context.getLocation(siteTarget),\n                    ISSUE_JSON_SITE_TARGET.getBriefDescription(TextFormat.TEXT),\n                    quickfixData =\n                      fix()\n                        .removeNode(context, jsonAnnotation.sourcePsi, text = \"${siteTarget.text}:\"),\n                  )\n                }\n\n                jsonAnnotationToValidate = jsonAnnotation\n              }\n            }\n\n            validateJsonName(context, parameter, jsonAnnotationToValidate, jsonNames)\n\n            if (jsonAnnotationToValidate == null) {\n              // If they already have an `@Json` annotation, don't warn because the IDE will already\n              // nag them separately about this with better renaming tools.\n              val name = sourcePsi.name ?: continue\n              val camelCase = name.snakeToCamel()\n              if (name != camelCase) {\n                val propKeyword = sourcePsi.valOrVarKeyword!!.text\n                context.report(\n                  ISSUE_SNAKE_CASE,\n                  context.getNameLocation(parameter as UElement),\n                  ISSUE_SNAKE_CASE.getBriefDescription(TextFormat.TEXT),\n                  quickfixData =\n                    fix()\n                      .replace()\n                      .name(\"Add @Json(name = \\\"$name\\\") and rename to '$camelCase'\")\n                      .range(context.getLocation(sourcePsi))\n                      .shortenNames()\n                      .text(\"$propKeyword $name\")\n                      .with(\"@$FQCN_JSON(name = \\\"$name\\\") $propKeyword $camelCase\")\n                      .autoFix()\n                      .build(),\n                )\n              }\n            }\n          } else {\n            if (!hasDefaultValue) {\n              context.report(\n                ISSUE_PARAM_NEEDS_INIT,\n                context.getLocation(parameter as UElement),\n                ISSUE_PARAM_NEEDS_INIT.getBriefDescription(TextFormat.TEXT),\n                quickfixData = null,\n              )\n            }\n          }\n        }\n      }\n\n      private fun checkSealedClass(node: UClass) {\n        val typeLabelAnnotation = node.getAnnotation(FQCN_TYPE_LABEL)\n        val defaultObjectAnnotation = node.getAnnotation(FQCN_DEFAULT_OBJECT)\n\n        if (typeLabelAnnotation != null && defaultObjectAnnotation != null) {\n          // Report both\n          context.report(\n            ISSUE_DOUBLE_TYPE_LABEL,\n            context.getNameLocation(typeLabelAnnotation),\n            ISSUE_DOUBLE_TYPE_LABEL.getBriefDescription(TextFormat.TEXT),\n            fix().removeNode(context, typeLabelAnnotation),\n          )\n          context.report(\n            ISSUE_DOUBLE_TYPE_LABEL,\n            context.getNameLocation(defaultObjectAnnotation),\n            ISSUE_DOUBLE_TYPE_LABEL.getBriefDescription(TextFormat.TEXT),\n            fix().removeNode(context, defaultObjectAnnotation),\n          )\n          return\n        }\n\n        val isTypeLabeled = typeLabelAnnotation != null\n        if (isTypeLabeled && node.hasTypeParameters()) {\n          context.report(\n            ISSUE_GENERIC_SEALED_SUBTYPE,\n            context.getLocation((node.sourcePsi as KtClass).typeParameterList!!),\n            ISSUE_GENERIC_SEALED_SUBTYPE.getBriefDescription(TextFormat.TEXT),\n          )\n        }\n\n        val isDefaultObjectLabeled = defaultObjectAnnotation != null\n\n        val isAnnotatedWithTypeLabelOrDefaultObject = isTypeLabeled || isDefaultObjectLabeled\n\n        // Collect all superTypes since interfaces can be sealed too!\n        // Filter out types in other packages as the compiler will enforce that for us.\n        val currentPackage = node.getContainingUFile()?.packageName ?: return\n        val sealedSuperTypeFound =\n          node.superTypes\n            .asSequence()\n            .mapNotNull { slackEvaluator.getTypeClass(it)?.toUElementOfType<UClass>() }\n            .filter { it.getContainingUFile()?.packageName == currentPackage }\n            .firstOrNull { superType ->\n              if (slackEvaluator.isSealed(superType)) {\n                val superJsonClassAnnotation = superType.findAnnotation(FQCN_JSON_CLASS)\n                if (superJsonClassAnnotation != null) {\n                  val generatorExpression = superJsonClassAnnotation.findAttributeValue(\"generator\")\n                  val generator = generatorExpression?.evaluate() as? String\n                  if (generator != null) {\n                    if (generator.startsWith(\"sealed:\")) {\n                      if (!isAnnotatedWithTypeLabelOrDefaultObject) {\n                        context.report(\n                          ISSUE_MISSING_TYPE_LABEL,\n                          context.getNameLocation(node),\n                          ISSUE_MISSING_TYPE_LABEL.getBriefDescription(TextFormat.TEXT),\n                          quickfixData = null,\n                        )\n                        return@firstOrNull true\n                      } else {\n                        return@firstOrNull true\n                      }\n                    }\n                  }\n                }\n              }\n              false\n            }\n\n        if (sealedSuperTypeFound != null) return\n\n        // If we've reached here and have annotations, something is wrong\n        if (isAnnotatedWithTypeLabelOrDefaultObject) {\n          if (typeLabelAnnotation != null) {\n            context.report(\n              ISSUE_INAPPROPRIATE_TYPE_LABEL,\n              context.getNameLocation(typeLabelAnnotation),\n              ISSUE_INAPPROPRIATE_TYPE_LABEL.getBriefDescription(TextFormat.TEXT),\n              quickfixData = fix().removeNode(context, typeLabelAnnotation),\n            )\n          }\n          if (defaultObjectAnnotation != null) {\n            context.report(\n              ISSUE_INAPPROPRIATE_TYPE_LABEL,\n              context.getNameLocation(defaultObjectAnnotation),\n              ISSUE_INAPPROPRIATE_TYPE_LABEL.getBriefDescription(TextFormat.TEXT),\n              quickfixData = fix().removeNode(context, defaultObjectAnnotation),\n            )\n          }\n        }\n      }\n    }\n  }\n\n  private fun validateAdaptedByAnnotation(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    adaptedByAnnotation: UAnnotation,\n  ) {\n    // Check the adapter is a valid adapter type\n    val adapterAttribute =\n      (adaptedByAnnotation.findAttributeValue(\"adapter\") as? KotlinUClassLiteralExpression)\n        ?: return\n    val targetType = adapterAttribute.type ?: return\n    val targetClass = evaluator.getTypeClass(targetType) ?: return\n    val implementsAdapter =\n      isInheritor(targetClass, true, FQCN_JSON_ADAPTER) ||\n        isInheritor(targetClass, true, FQCN_JSON_ADAPTER_FACTORY)\n    if (!implementsAdapter) {\n      context.report(\n        ISSUE_ADAPTED_BY_REQUIRES_ADAPTER,\n        context.getLocation(adapterAttribute),\n        ISSUE_ADAPTED_BY_REQUIRES_ADAPTER.getBriefDescription(TextFormat.TEXT),\n        quickfixData = null,\n      )\n    } else if (!targetClass.hasAnnotation(\"androidx.annotation.Keep\")) {\n      context.report(\n        ISSUE_ADAPTED_BY_REQUIRES_KEEP,\n        context.getLocation(adapterAttribute),\n        ISSUE_ADAPTED_BY_REQUIRES_KEEP.getBriefDescription(TextFormat.TEXT),\n        quickfixData = null,\n      )\n    }\n  }\n\n  private fun checkMoshiType(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    psiType: PsiType,\n    parameter: UParameter,\n    typeNode: UElement,\n    defaultValueExpression: KtExpression?,\n    nestedGenericCheck: Boolean = true,\n  ) {\n\n    if (psiType is PsiPrimitiveType) return\n\n    if (psiType is PsiArrayType) {\n      val componentType = psiType.componentType\n      val componentTypeName =\n        if (componentType is PsiPrimitiveType) {\n          componentType.boxedTypeName!!.let {\n            if (it == \"java.lang.Integer\") \"Int\" else it.removePrefix(\"java.lang.\")\n          }\n        } else {\n          typeNode.sourcePsi!!.text.trim().removePrefix(\"Array<\").removeSuffix(\">\")\n        }\n      val replacement = \"List<$componentTypeName>\"\n      context.report(\n        ISSUE_ARRAY,\n        context.getLocation(typeNode),\n        ISSUE_ARRAY.getBriefDescription(TextFormat.TEXT),\n        quickfixData =\n          fix()\n            .replace()\n            .name(\"Change to $replacement\")\n            .range(context.getLocation(typeNode))\n            .shortenNames()\n            .text(typeNode.sourcePsi!!.text)\n            .with(replacement)\n            .autoFix()\n            .build()\n            .takeUnless { nestedGenericCheck },\n      )\n      return\n    }\n\n    if (psiType !is PsiClassType) return\n\n    val psiClass =\n      evaluator.getTypeClass(psiType)\n        ?: error(\n          \"Could not load class for ${psiType.className} on ${parameter.getUastParentOfType<UClass>()!!.name}.${parameter.name}\"\n        )\n\n    if (psiClass is PsiTypeParameter) return\n\n    if (psiClass.isString() || psiClass.isObjectOrAny() || psiClass.isBoxedPrimitive()) return\n\n    if (psiClass.isEnum) {\n      if (!psiClass.hasMoshiAnnotation()) {\n        context.report(\n          ISSUE_ENUM_PROPERTY_COULD_BE_MOSHI,\n          context.getLocation(typeNode),\n          ISSUE_ENUM_PROPERTY_COULD_BE_MOSHI.getBriefDescription(TextFormat.TEXT),\n          quickfixData = null,\n        )\n      } else if (\n        defaultValueExpression is KtReferenceExpression ||\n          defaultValueExpression is KtQualifiedExpression\n      ) {\n        val defaultValueText = defaultValueExpression.text\n        if (\"UNKNOWN\" in defaultValueText) {\n          val oldText = \" = $defaultValueText\"\n          context.report(\n            ISSUE_ENUM_PROPERTY_DEFAULT_UNKNOWN,\n            context.getLocation(defaultValueExpression),\n            ISSUE_ENUM_PROPERTY_DEFAULT_UNKNOWN.getBriefDescription(TextFormat.TEXT),\n            quickfixData =\n              fix()\n                .replace()\n                .name(\"Remove '$oldText'\")\n                .range(context.getLocation(parameter as UElement))\n                .shortenNames()\n                .text(oldText)\n                .with(\"\")\n                .autoFix()\n                .build()\n                .takeUnless { nestedGenericCheck },\n          )\n        }\n      }\n      return\n    }\n\n    // Check collections\n    val isJsonCollection =\n      psiClass.qualifiedName == \"java.util.List\" ||\n        psiClass.qualifiedName == \"java.util.Collection\" ||\n        psiClass.qualifiedName == \"java.util.Set\" ||\n        psiClass.qualifiedName == \"java.util.Map\"\n    if (isJsonCollection) {\n\n      // Do a fuzzy check for mutability. PSI doesn't tell us if they're Kotlin mutable types so we\n      // look at the source manually.\n      val source = typeNode.sourcePsi!!.text\n      val correctedImmutableType =\n        when {\n          source.startsWith(\"MutableCollection\") -> \"Collection\"\n          source.startsWith(\"MutableList\") -> \"List\"\n          source.startsWith(\"MutableSet\") -> \"Set\"\n          source.startsWith(\"MutableMap\") -> \"Map\"\n          else -> null\n        }\n      if (correctedImmutableType != null) {\n        context.report(\n          ISSUE_MUTABLE_COLLECTIONS,\n          context.getLocation(typeNode),\n          ISSUE_MUTABLE_COLLECTIONS.getBriefDescription(TextFormat.TEXT),\n          quickfixData =\n            fix()\n              .replace()\n              .name(\"Change to $correctedImmutableType\")\n              .range(context.getLocation(typeNode))\n              .shortenNames()\n              .text(\"Mutable$correctedImmutableType<\")\n              .with(\"$correctedImmutableType<\")\n              .autoFix()\n              .build()\n              .takeUnless { nestedGenericCheck },\n        )\n      }\n\n      for (typeArg in psiType.parameters) {\n        // It's generic, check each generic.\n        // TODO we currently report the whole type even if it's just one bad generic. Ideally\n        //  we just underline the generic type but as mentioned higher up this is hard in PSI.\n        //  Requires separately mapping PSI type args to the modeled versions. Example below:\n        //    val argsPsi = (typeNode.sourcePsi!!.children[0] as KtUserType).typeArguments\n        checkMoshiType(context, evaluator, typeArg, parameter, typeNode, null)\n      }\n      return\n    }\n\n    when {\n      isInheritor(psiClass, \"java.util.Collection\") -> {\n        // A second, more flexible collection check that can suggest better alternatives\n        val source = typeNode.sourcePsi!!.text\n        val type =\n          when {\n            \"Collection\" in source -> \"Collection\"\n            \"List\" in source -> \"List\"\n            \"Set\" in source -> \"Set\"\n            else -> null\n          }\n        val fix =\n          if (type == null) {\n            null\n          } else {\n            fix()\n              .replace()\n              .name(\"Change to $type\")\n              .range(context.getLocation(typeNode))\n              .shortenNames()\n              .text(source.substringBefore(\"<\"))\n              .with(type)\n              .autoFix()\n              .build()\n              .takeUnless { nestedGenericCheck }\n          }\n        context.report(\n          ISSUE_NON_MOSHI_CLASS_COLLECTION,\n          context.getLocation(typeNode),\n          ISSUE_NON_MOSHI_CLASS_COLLECTION.getBriefDescription(TextFormat.TEXT)\n            .withHint(psiClass.name),\n          quickfixData = fix,\n        )\n      }\n      isInheritor(psiClass, \"java.util.Map\") -> {\n        // A second, more flexible map check that can suggest better alternatives\n        context.report(\n          ISSUE_NON_MOSHI_CLASS_MAP,\n          context.getLocation(typeNode),\n          ISSUE_NON_MOSHI_CLASS_MAP.getBriefDescription(TextFormat.TEXT).withHint(psiClass.name),\n          quickfixData =\n            fix()\n              .replace()\n              .name(\"Change to Map\")\n              .range(context.getLocation(typeNode))\n              .shortenNames()\n              .text(typeNode.sourcePsi!!.text.substringBefore(\"<\"))\n              .with(\"Map\")\n              .autoFix()\n              .build()\n              .takeUnless { nestedGenericCheck },\n        )\n      }\n      psiClass.isPlatformType() -> {\n        // Warn because this isn't supported\n        // Eventually error when gson interop is out\n        context.report(\n          ISSUE_NON_MOSHI_CLASS_PLATFORM,\n          context.getLocation(typeNode),\n          ISSUE_NON_MOSHI_CLASS_PLATFORM.getBriefDescription(TextFormat.TEXT)\n            .withHint(psiClass.name),\n          quickfixData = null,\n        )\n      }\n      psiClass.hasMoshiAnnotation() -> {\n        if (psiType.hasParameters()) {\n          for (typeArg in psiType.parameters) {\n            // It's generic, check each generic.\n            // TODO we currently report the whole type even if it's just one bad generic. Ideally\n            //  we just underline the generic type but as mentioned higher up this is hard in PSI\n            checkMoshiType(context, evaluator, typeArg, parameter, typeNode, null)\n          }\n        } else {\n          return\n        }\n      }\n      else -> {\n        if (psiClass.qualifiedName?.startsWith(\"slack\") == true) {\n          // Slack class, suggest making it JsonClass\n          context.report(\n            ISSUE_NON_MOSHI_CLASS_INTERNAL,\n            context.getLocation(typeNode),\n            ISSUE_NON_MOSHI_CLASS_INTERNAL.getBriefDescription(TextFormat.TEXT)\n              .withHint(psiClass.name),\n            quickfixData = null,\n          )\n        } else {\n          // Other class, error not supported\n          context.report(\n            ISSUE_NON_MOSHI_CLASS_EXTERNAL,\n            context.getLocation(typeNode),\n            ISSUE_NON_MOSHI_CLASS_EXTERNAL.getBriefDescription(TextFormat.TEXT)\n              .withHint(psiClass.name),\n            quickfixData = null,\n          )\n        }\n      }\n    }\n  }\n\n  /**\n   * Validates @Json name annotation usage and also checks for `@SerializedName` usage. Returns the\n   * Json.name value, if any.\n   */\n  private fun <T> validateJsonName(\n    context: JavaContext,\n    member: T,\n    jsonAnnotation: UAnnotation?,\n    seenNames: MutableMap<String, PsiNamedElement>,\n  ) where T : UAnnotated, T : PsiNamedElement {\n    var jsonNameValue: String? = null\n    if (jsonAnnotation != null) {\n      val jsonNameAttr = jsonAnnotation.findAttributeValue(\"name\")\n      val jsonName = jsonNameAttr?.evaluate() as? String\n      when {\n        jsonName == null -> {\n          // Ignored, sometimes UAST stubs an incomplete annotation without members\n        }\n        jsonName.isBlank() -> {\n          context.report(\n            ISSUE_BLANK_JSON_NAME,\n            context.getLocation(jsonNameAttr),\n            ISSUE_BLANK_JSON_NAME.getBriefDescription(TextFormat.TEXT),\n          )\n        }\n        jsonName == member.name -> {\n          context.report(\n            ISSUE_REDUNDANT_JSON_NAME,\n            context.getLocation(jsonNameAttr),\n            ISSUE_REDUNDANT_JSON_NAME.getBriefDescription(TextFormat.TEXT),\n            quickfixData = fix().removeNode(context, jsonAnnotation.sourcePsi!!),\n          )\n        }\n        else -> {\n          // Save this to compare to SerializedName later\n          jsonNameValue = jsonName\n        }\n      }\n    }\n\n    val jsonName = jsonNameValue ?: member.name!!\n\n    seenNames.put(jsonName, member)?.let { existingMember ->\n      // Report both\n      context.report(\n        ISSUE_DUPLICATE_JSON_NAME,\n        context.getNameLocation(member as PsiElement),\n        \"Name '$jsonName' is duplicated by member '${existingMember.name}'.\",\n      )\n      context.report(\n        ISSUE_DUPLICATE_JSON_NAME,\n        context.getNameLocation(existingMember),\n        \"Name '$jsonName' is duplicated by member '${member.name}'.\",\n      )\n    }\n\n    // Check for a leftover `@SerializedName`\n    member.findAnnotation(FQCN_SERIALIZED_NAME)?.let { serializedName ->\n      val name = serializedName.findAttributeValue(\"value\")?.evaluate() as String\n      val alternateCount =\n        (serializedName.findAttributeValue(\"alternate\")?.sourcePsi\n            as? KtCollectionLiteralExpression)\n          ?.getInnerExpressions()\n          ?.size ?: -1\n      val hasAlternates = alternateCount > 0\n\n      var fix: LintFix? = null\n      // If alternates are present, offer no suggestion because there's no equivalent in @Json\n      // If both are present and have the same value, offer to delete it\n      // If both are present with different values, offer no fix, developer needs to reconcile\n      // If only @SerializedName is present, offer to replace with @Json\n      if (jsonAnnotation != null) {\n        if (jsonNameValue == name && !hasAlternates) {\n          fix = fix().removeNode(context, serializedName.sourcePsi!!)\n        }\n      } else if (!hasAlternates) {\n        fix =\n          fix()\n            .replace()\n            .name(\"Replace with @Json(name = \\\"$name\\\")\")\n            .range(context.getLocation(serializedName))\n            .shortenNames()\n            .text(serializedName.sourcePsi!!.text)\n            .with(\"@$FQCN_JSON(name = \\\"$name\\\")\")\n            .autoFix()\n            .build()\n      }\n      context.report(\n        ISSUE_SERIALIZED_NAME,\n        context.getLocation(serializedName),\n        ISSUE_SERIALIZED_NAME.getBriefDescription(TextFormat.TEXT),\n        quickfixData = fix,\n      )\n    }\n  }\n\n  /** A simple check for `@JsonQualifier` annotation classes. */\n  private fun checkJsonQualifierAnnotation(context: JavaContext, node: UClass) {\n    if (!node.hasAnnotation(FQCN_JSON_QUALIFIER)) return\n\n    // JsonQualifier annotations must have RUNTIME retention and support targeting FIELD\n    // Try both the Kotlin and Java annotations. In Kotlin we try both\n    val retentionAnnotationPair: Pair<String, String>\n    val targetAnnotationPair: Pair<String, String>\n    val isKotlin = isKotlin(node.language)\n    if (isKotlin) {\n      retentionAnnotationPair = \"kotlin.annotation.Retention\" to \"value\"\n      targetAnnotationPair = \"kotlin.annotation.Target\" to \"allowedTargets\"\n    } else {\n      retentionAnnotationPair = \"java.lang.annotation.Retention\" to \"value\"\n      targetAnnotationPair = \"java.lang.annotation.Target\" to \"value\"\n    }\n\n    node.findAnnotation(retentionAnnotationPair.first)?.let { retentionAnnotation ->\n      val retentionValue =\n        retentionAnnotation\n          .findAttributeValue(retentionAnnotationPair.second)\n          ?.unwrapSimpleNameReferenceExpression()\n          .run {\n            this\n              ?: if (isKotlin) {\n                // Undefined would be weird but ok, default again is RUNTIME\n                return@let\n              } else {\n                // Always required in Java\n                error(\"Not possible\")\n              }\n          }\n      if (retentionValue.identifier != \"RUNTIME\") {\n        val fix =\n          if (isKotlin) {\n            // Fix is just to remove it because RUNTIME is the default in Kotlin.\n            fix().removeNode(context, retentionAnnotation.sourcePsi!!)\n          } else {\n            // Java we need to change it\n            fix()\n              .replace()\n              .name(\"Replace with RUNTIME\")\n              .range(context.getLocation(retentionValue))\n              .shortenNames()\n              .text(retentionValue.identifier)\n              .with(\"RUNTIME\")\n              .autoFix()\n              .build()\n          }\n        context.report(\n          ISSUE_QUALIFIER_RETENTION,\n          context.getLocation(retentionAnnotation.sourcePsi!!),\n          ISSUE_QUALIFIER_RETENTION.getBriefDescription(TextFormat.TEXT),\n          quickfixData = fix,\n        )\n      }\n    }\n      ?: run {\n        if (!isKotlin) {\n          // Default in Kotlin when unannotated is RUNTIME but not in Java!\n          // TODO can we add it for them?\n          context.report(\n            ISSUE_QUALIFIER_RETENTION,\n            context.getNameLocation(node),\n            ISSUE_QUALIFIER_RETENTION.getBriefDescription(TextFormat.TEXT),\n          )\n        }\n      }\n\n    // It's ok if Target is missing, default in both languages includes FIELD\n    node.findAnnotation(targetAnnotationPair.first)?.let { targetAnnotation ->\n      val targetValues =\n        when (\n          val targetsAttr = targetAnnotation.findAttributeValue(targetAnnotationPair.second)!!\n        ) {\n          is UCallExpression -> {\n            // Covers all of these cases\n            // @Target(FIELD, PROPERTY)\n            // @Target([FIELD, PROPERTY])\n            // @Target({FIELD, PROPERTY})\n            targetsAttr.valueArguments\n          }\n          is UReferenceExpression -> {\n            // @Target(FIELD)\n            listOf(targetsAttr)\n          }\n          else -> {\n            error(\"Unrecognized annotation attr value: $targetsAttr\")\n          }\n        }\n      if (targetValues.none { it.unwrapSimpleNameReferenceExpression().identifier == \"FIELD\" }) {\n        context.report(\n          ISSUE_QUALIFIER_TARGET,\n          context.getLocation(targetAnnotation.sourcePsi!!),\n          ISSUE_QUALIFIER_TARGET.getBriefDescription(TextFormat.TEXT),\n        )\n      }\n    }\n  }\n\n  /**\n   * A simple check for a number of issues related to enum use in Moshi.\n   *\n   * In short - enums serialized with Moshi _must_ meet the following requirements:\n   * - Be annotated with `@JsonClass`.\n   * - Reserve their first member as `UNKNOWN` for handling unrecognized enums.\n   *\n   * This lint will attempt to detect if an enum is used with Moshi and check these requirements.\n   * They should not generate adapters (i.e. `JsonClass.generateAdapter` should be `false`) but are\n   * exempt from that requirement and the requirements of this lint in general if they define a\n   * custom `JsonClass.generator` value.\n   */\n  private fun checkEnum(context: JavaContext, node: UClass) {\n    val jsonClassAnnotation = node.findAnnotation(FQCN_JSON_CLASS)\n    val constants = node.uastDeclarations.filterIsInstance<UEnumConstant>()\n    val hasJsonAnnotatedConstant = constants.any { it.hasAnnotation(FQCN_JSON) }\n    val isPresumedMoshi = jsonClassAnnotation != null || hasJsonAnnotatedConstant\n\n    // If it's not annotated and no members have @Json, it's not a moshi class\n    if (!isPresumedMoshi) return\n\n    // Check that generateAdapter is false\n    var usesCustomGenerator = false\n    if (jsonClassAnnotation != null) {\n      // Check the `JsonClass.generator` property first\n      // If generator is an empty string (the default), then it's a standard enum.\n      // If it were something else, a generator is claiming to generate something for it\n      // and we choose to ignore it in that case.\n      // This can sometimes come back as the default empty String and sometimes as null if not\n      // defined\n      val generator = jsonClassAnnotation.findAttributeValue(\"generator\")?.evaluate() as? String\n      usesCustomGenerator = !generator.isNullOrBlank()\n      if (!usesCustomGenerator) {\n        val generateAdapter =\n          jsonClassAnnotation.findAttributeValue(\"generateAdapter\") as ULiteralExpression\n        if (generateAdapter.evaluate() != false) {\n          context.report(\n            ISSUE_ENUM_JSON_CLASS_GENERATED,\n            context.getLocation(generateAdapter),\n            ISSUE_ENUM_JSON_CLASS_GENERATED.getBriefDescription(TextFormat.TEXT),\n            fix()\n              .replace()\n              .name(\"Set to false\")\n              .text(generateAdapter.asSourceString())\n              .with(\"false\")\n              .autoFix()\n              .build(),\n          )\n        }\n      }\n    } else {\n      // If an @Json is present but not @JsonClass, suggest it\n      context.report(\n        ISSUE_ENUM_JSON_CLASS_MISSING,\n        context.getNameLocation(node),\n        ISSUE_ENUM_JSON_CLASS_MISSING.getBriefDescription(TextFormat.TEXT),\n        // TODO can we add it for them?\n        quickfixData = null,\n      )\n    }\n\n    // If they're using a custom generator, peace of out this\n    if (usesCustomGenerator) return\n\n    // Visit members, ensure first is UNKNOWN if annotated\n    val unknownIndex = constants.indexOfFirst { it.name == \"UNKNOWN\" }\n    if (unknownIndex == -1) {\n      context.report(\n        ISSUE_ENUM_UNKNOWN,\n        context.getNameLocation(node),\n        ISSUE_ENUM_UNKNOWN.getBriefDescription(TextFormat.TEXT),\n        quickfixData = null, // TODO can we add an enum for them?\n      )\n    } else {\n      val constant = constants[unknownIndex]\n      if (unknownIndex != 0) {\n        context.report(\n          ISSUE_ENUM_UNKNOWN,\n          context.getNameLocation(constant),\n          ISSUE_ENUM_UNKNOWN.getBriefDescription(TextFormat.TEXT),\n          quickfixData = null, // TODO can we reorder it for them?\n        )\n      } else {\n        val jsonAnnotation = constant.getAnnotation(FQCN_JSON)\n        if (jsonAnnotation != null) {\n          val source = jsonAnnotation.text\n          context.report(\n            ISSUE_ENUM_ANNOTATED_UNKNOWN,\n            context.getNameLocation(node),\n            ISSUE_ENUM_ANNOTATED_UNKNOWN.getBriefDescription(TextFormat.TEXT),\n            fix()\n              .replace()\n              .name(\"Remove @Json\")\n              .range(context.getLocation(jsonAnnotation))\n              .shortenNames()\n              .text(source)\n              .with(\"\")\n              .autoFix()\n              .build(),\n          )\n        }\n      }\n    }\n\n    val jsonNames = mutableMapOf<String, PsiNamedElement>()\n    for ((index, constant) in constants.withIndex()) {\n      if (index == unknownIndex) continue\n\n      val jsonAnnotation = constant.findAnnotation(FQCN_JSON)\n      validateJsonName(context, constant, jsonAnnotation, jsonNames)\n\n      val name = constant.name\n      val screamingSnake = name.toScreamingSnakeCase()\n      if (name != screamingSnake) {\n        // Only suggest a new Json annotation if one isn't already present\n        val fixName: String\n        val fixReplacement: String\n        if (jsonAnnotation == null) {\n          fixName = \"Add @Json(name = \\\"$name\\\") and rename to '$screamingSnake'\"\n          fixReplacement = \"@$FQCN_JSON(name = \\\"$name\\\") $screamingSnake\"\n        } else {\n          fixName = \"Rename to '$screamingSnake'\"\n          fixReplacement = screamingSnake\n        }\n        context.report(\n          ISSUE_ENUM_CASING,\n          context.getNameLocation(constant),\n          ISSUE_ENUM_CASING.getBriefDescription(TextFormat.TEXT),\n          quickfixData =\n            fix()\n              .replace()\n              .name(fixName)\n              .range(context.getNameLocation(constant))\n              .shortenNames()\n              .text(name)\n              .with(fixReplacement)\n              .autoFix()\n              .build(),\n        )\n      }\n    }\n  }\n\n  companion object {\n    private const val FQCN_ADAPTED_BY = \"dev.zacsweers.moshix.adapters.AdaptedBy\"\n    private const val FQCN_JSON_CLASS = \"com.squareup.moshi.JsonClass\"\n    private const val FQCN_JSON = \"com.squareup.moshi.Json\"\n    private const val FQCN_JSON_ADAPTER = \"com.squareup.moshi.JsonAdapter\"\n    private const val FQCN_JSON_ADAPTER_FACTORY = \"com.squareup.moshi.JsonAdapter.Factory\"\n    private const val FQCN_JSON_QUALIFIER = \"com.squareup.moshi.JsonQualifier\"\n    private const val FQCN_TYPE_LABEL = \"dev.zacsweers.moshix.sealed.annotations.TypeLabel\"\n    private const val FQCN_DEFAULT_OBJECT = \"dev.zacsweers.moshix.sealed.annotations.DefaultObject\"\n    private const val FQCN_SERIALIZED_NAME = \"com.google.gson.annotations.SerializedName\"\n\n    private val REQUIRED_VISIBILITIES = setOf(KtTokens.PUBLIC_KEYWORD, KtTokens.INTERNAL_KEYWORD)\n\n    // This hint mechanism is solely to help identify nested type arguments in the error message\n    // since we can't easily highlight them in nested generics. We can remove this if we figure\n    // out a good way to do it.\n    private const val HINT = \"%HINT%\"\n\n    private fun String.withHint(hint: String?): String {\n      return replace(HINT, hint.orEmpty())\n    }\n\n    private fun createIssue(\n      subId: String,\n      briefDescription: String,\n      explanation: String,\n      severity: Severity = Severity.ERROR,\n    ): Issue =\n      Issue.create(\n        \"MoshiUsage$subId\",\n        briefDescription,\n        explanation,\n        Category.CORRECTNESS,\n        6,\n        severity,\n        implementation = sourceImplementation<MoshiUsageDetector>(),\n      )\n\n    private val ISSUE_MISSING_TYPE_LABEL =\n      createIssue(\n        \"MissingTypeLabel\",\n        \"Sealed Moshi subtypes must be annotated with @TypeLabel or @DefaultObject.\",\n        \"\"\"\n          moshi-sealed requires sealed subtypes to be annotated with @TypeLabel or @DefaultObject. \\\n          Otherwise, moshi-sealed will fail to compile.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_BLANK_GENERATOR =\n      createIssue(\n        \"BlankGenerator\",\n        \"Don't use blank JsonClass.generator values.\",\n        \"\"\"\n          The default for JsonClass.generator is \"\", it's redundant to specify an empty one and an \\\n          error to specify a blank one.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_BLANK_TYPE_LABEL =\n      createIssue(\n        \"BlankTypeLabel\",\n        \"Moshi-sealed requires a type label specified after the 'sealed:' prefix.\",\n        \"Moshi-sealed requires a type label specified after the 'sealed:' prefix.\",\n      )\n\n    private val ISSUE_SEALED_MUST_BE_SEALED =\n      createIssue(\n        \"SealedMustBeSealed\",\n        \"Moshi-sealed can only be applied to 'sealed' types.\",\n        \"Moshi-sealed can only be applied to 'sealed' types.\",\n      )\n\n    private val ISSUE_GENERATE_ADAPTER_SHOULD_BE_TRUE =\n      createIssue(\n        \"GenerateAdapterShouldBeTrue\",\n        \"JsonClass.generateAdapter must be true in order for Moshi code gen to run.\",\n        \"JsonClass.generateAdapter must be true in order for Moshi code gen to run.\",\n      )\n\n    private val ISSUE_PRIVATE_CONSTRUCTOR =\n      createIssue(\n        \"PrivateConstructor\",\n        \"Constructors in Moshi classes cannot be private.\",\n        \"\"\"\n          Constructors in Moshi classes cannot be private. \\\n          Otherwise Moshi cannot invoke it during decoding.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_PRIVATE_PARAMETER =\n      createIssue(\n        \"PrivateConstructorProperty\",\n        \"Constructor parameter properties in Moshi classes cannot be private.\",\n        \"\"\"\n          Constructor parameter properties in Moshi classes cannot be private. \\\n          Otherwise these properties will not be visible in serialization.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_TRANSIENT_NEEDS_INIT =\n      createIssue(\n        \"TransientNeedsInit\",\n        \"Transient constructor properties must have default values.\",\n        \"\"\"\n          Transient constructor property parameters in Moshi classes must have default values. Since \\\n          these parameters do not participate in serialization, Moshi cannot fulfill them otherwise \\\n          during construction.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_INAPPROPRIATE_TYPE_LABEL =\n      createIssue(\n        \"InappropriateTypeLabel\",\n        \"Inappropriate @TypeLabel or @DefaultObject annotation.\",\n        \"\"\"\n          This class declares a @TypeLabel or @DefaultObject annotation but does not appear to \\\n          subclass a sealed Moshi type. Please remove these annotations or extend the appropriate \\\n          sealed Moshi-serialized class.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_DOUBLE_TYPE_LABEL =\n      createIssue(\n        \"DoubleTypeLabel\",\n        \"Only use one of @TypeLabel or @DefaultObject.\",\n        \"\"\"\n          Only one of @TypeLabel and @DefaultObject annotations should be present. It is an error to \\\n          declare both!\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_GENERIC_SEALED_SUBTYPE =\n      createIssue(\n        \"GenericSealedSubtype\",\n        \"Sealed subtypes used with moshi-sealed cannot be generic.\",\n        \"\"\"\n          Moshi has no way of conveying generics information to sealed subtypes when we create an \\\n          adapter from the base type. As a result, you should remove generics from this subtype.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_DOUBLE_CLASS_ANNOTATION =\n      createIssue(\n        \"DoubleClassAnnotation\",\n        \"Only use one of @AdaptedBy or @JsonClass.\",\n        \"\"\"\n          Only one of @AdaptedBy and @JsonClass annotations should be present. It is an error to \\\n          declare both!\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_VISIBILITY =\n      createIssue(\n        \"ClassVisibility\",\n        \"@JsonClass-annotated types must be public, package-private, or internal.\",\n        \"\"\"\n          @JsonClass-annotated types must be public, package-private, or internal. Otherwise, Moshi\n          will not be able to access them from generated adapters.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_PARAM_NEEDS_INIT =\n      createIssue(\n        \"ParamNeedsInit\",\n        \"Constructor non-property parameters in Moshi classes must have default values.\",\n        \"\"\"\n          Constructor non-property parameters in Moshi classes must have default values. Since these \\\n          parameters do not participate in serialization, Moshi cannot fulfill them otherwise during \\\n          construction.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_BLANK_JSON_NAME =\n      createIssue(\n        \"BlankJsonName\",\n        \"Don't use blank names in `@Json`.\",\n        \"\"\"\n          Blank names in `@Json`, while technically legal, are likely a programmer error and\n          likely to cause encoding issues.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_REDUNDANT_JSON_NAME =\n      createIssue(\n        \"RedundantJsonName\",\n        \"Json.name with the same value as the property/enum member name is redundant.\",\n        \"\"\"\n          Redundant Json.name values can make code noisier and harder to read, consider removing it \\\n          or suppress this warning with a commented suppression explaining why it's needed.\n        \"\"\"\n          .trimIndent(),\n        severity = Severity.WARNING,\n      )\n\n    private val ISSUE_SERIALIZED_NAME =\n      createIssue(\n        \"SerializedName\",\n        \"Use Moshi's @Json rather than Gson's @SerializedName.\",\n        \"\"\"\n          @SerializedName is specific to Gson and will not work with Moshi. Replace it with Moshi's \\\n          equivalent @Json annotation instead (or remove it if @Json is defined already).\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_QUALIFIER_RETENTION =\n      createIssue(\n        \"QualifierRetention\",\n        \"JsonQualifiers must have RUNTIME retention.\",\n        \"\"\"\n          Moshi uses these annotations at runtime, as such they must be available at runtime. In \\\n          Kotlin, this is the default and you can just remove the Retention annotation. In Java, \\\n          you must specify it explicitly with @Retention(RUNTIME).\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_QUALIFIER_TARGET =\n      createIssue(\n        \"QualifierTarget\",\n        \"JsonQualifiers must include FIELD targeting.\",\n        \"\"\"\n          Moshi code gen stores these annotations on generated adapter fields, as such they must be \\\n          allowed on fields. Please specify it explicitly as @Target(FIELD).\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_JSON_SITE_TARGET =\n      createIssue(\n        \"RedundantSiteTarget\",\n        \"Use of site-targets on @Json are redundant.\",\n        \"\"\"\n          Use of site-targets on @Json are redundant and can be removed. Only one, target-less @Json \\\n          annotation is necessary.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_OBJECT =\n      createIssue(\n        \"Object\",\n        \"Object types cannot be annotated with @JsonClass.\",\n        \"\"\"\n          Object types cannot be annotated with @JsonClass. The only way they are permitted to \\\n          participate in Moshi serialization is if they are a sealed subtype of a Moshi sealed \\\n          class and annotated with `@TypeLabel` or `@DefaultObject` accordingly.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_USE_DATA =\n      createIssue(\n        \"UseData\",\n        \"Model classes should be immutable data classes.\",\n        \"\"\"\n          @JsonClass-annotated models should be immutable data classes unless there's a very \\\n          specific reason not to. If you want a custom equals/hashcode/toString impls, make it data \\\n          anyway and override the ones you need. If you want non-property parameter values, consider \\\n          making them `@Transient` properties instead with default values.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_UNSUPPORTED_TYPE =\n      createIssue(\n        \"UnsupportedType\",\n        \"This type cannot be annotated with @JsonClass.\",\n        \"\"\"\n          Abstract, interface, annotation, and inner class types cannot be annotated with @JsonClass. \\\n          If you intend to decode this with a custom adapter, use @AdaptedBy.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_ADAPTED_BY_REQUIRES_ADAPTER =\n      createIssue(\n        \"AdaptedByRequiresAdapter\",\n        \"@AdaptedBy.adapter must be a JsonAdapter or JsonAdapter.Factory.\",\n        \"\"\"\n          @AdaptedBy.adapter must be a subclass of JsonAdapter or implement JsonAdapter.Factory.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_ADAPTED_BY_REQUIRES_KEEP =\n      createIssue(\n        \"AdaptedByRequiresKeep\",\n        \"Adapters targeted by @AdaptedBy must have @Keep.\",\n        \"\"\"\n          Adapters targeted by @AdaptedBy must be annotated with @Keep in order to be reflectively \\\n          looked up at runtime.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_MISSING_PRIMARY =\n      createIssue(\n        \"MissingPrimary\",\n        \"@JsonClass-annotated types must have a primary constructor or be sealed.\",\n        \"\"\"\n          @JsonClass-annotated types must have a primary constructor or be sealed. Otherwise, they \\\n          either have no serializable properties or all the potentially serializable properties are \\\n          mutable (which is not a case we want!).\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_ENUM_PROPERTY_COULD_BE_MOSHI =\n      createIssue(\n        \"EnumPropertyCouldBeMoshi\",\n        \"Consider making enum properties also use Moshi.\",\n        \"\"\"\n          While we have Gson interop, it's convenient to move enums used by Moshi classes to also \\\n          use Moshi so that you can leverage built-in support for UNKNOWN handling and get lint \\\n          checks for it. Simply add `@JsonClass` to this enum class and the appropriate lint will \\\n          guide you.\n        \"\"\"\n          .trimIndent(),\n        Severity.WARNING,\n      )\n\n    private val ISSUE_ENUM_PROPERTY_DEFAULT_UNKNOWN =\n      createIssue(\n        \"EnumPropertyDefaultUnknown\",\n        \"Suspicious default value to 'UNKNOWN' for a Moshi enum.\",\n        \"\"\"\n          The enum type of this property is handled by Moshi. This means it will default to \\\n          'UNKNOWN' if an unrecognized enum is encountered in decoding. At best, it is redundant to \\\n          default a property to this value. At worst, it can change nullability semantics if the \\\n          enum should actually allow nullable values or null on absence.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_VAR_PROPERTY =\n      createIssue(\n        \"VarProperty\",\n        \"Moshi properties should be immutable.\",\n        \"\"\"\n          While var properties are technically possible, they should not be used with Moshi classes \\\n          as it can lead to asymmetric encoding and thread-safety issues. Consider making this val.\n        \"\"\"\n          .trimIndent(),\n        Severity.WARNING,\n      )\n\n    private val ISSUE_SNAKE_CASE =\n      createIssue(\n        \"SnakeCase\",\n        \"Consider using `@Json(name = ...)` rather than direct snake casing.\",\n        \"\"\"\n          Moshi offers `@Json` annotations to specify names to use in JSON serialization, similar \\\n          to Gson's `@SerializedName`. This can help avoid snake_case properties in source directly.\n        \"\"\"\n          .trimIndent(),\n        Severity.WARNING,\n      )\n\n    private val ISSUE_DUPLICATE_JSON_NAME =\n      createIssue(\n        \"DuplicateJsonName\",\n        \"Duplicate JSON names are errors as JSON does not allow duplicate keys in objects.\",\n        \"\"\"\n          Duplicate JSON names are errors as JSON does not allow duplicate keys in objects.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_NON_MOSHI_CLASS_PLATFORM =\n      createIssue(\n        \"NonMoshiClassPlatform\",\n        \"Platform type '$HINT' is not natively supported by Moshi.\",\n        \"\"\"\n          The property type is a platform type (i.e. from java.*, kotlin.*, android.*). Moshi only \\\n          natively supports a small subset of these (primitives, String, and collection interfaces). \\\n          Otherwise, moshi-gson-interop will hand serialization of this property to Gson, which may \\\n          or may not handle it. This will eventually become an error after GSON is removed.\n        \"\"\"\n          .trimIndent(),\n        Severity.WARNING,\n      )\n\n    private val ISSUE_ARRAY =\n      createIssue(\n        \"Array\",\n        \"Prefer List over Array.\",\n        \"\"\"\n          Array types are not supported by Moshi, please use a List instead. Arrays are expensive to \\\n          manage in JSON as we don't know lengths ahead of time and they are a mutable code smell in \\\n          what should be immutable value classes. Otherwise, moshi-gson-interop will hand \\\n          serialization of this property to Gson, which may or may not handle it. This will \\\n          eventually become an error after GSON is removed.\n        \"\"\"\n          .trimIndent(),\n        Severity.WARNING,\n      )\n\n    private val ISSUE_MUTABLE_COLLECTIONS =\n      createIssue(\n        \"MutableCollections\",\n        \"Use immutable collections rather than mutable versions.\",\n        \"\"\"\n          While mutable collections are technically possible, they should not be used with Moshi \\\n          classes as it can lead to asymmetric encoding and thread-safety issues. Please make them \\\n          immutable versions instead.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    private val ISSUE_NON_MOSHI_CLASS_COLLECTION =\n      createIssue(\n        \"NonMoshiClassCollection\",\n        \"Concrete Collection type '$HINT' is not natively supported by Moshi.\",\n        \"\"\"\n          The property type is concrete Collection type (i.e. ArrayList, HashSet, etc). Moshi only \\\n          natively supports their interface types (List, Set, etc). Consider upcasting to the\n          interface type. Otherwise, moshi-gson-interop will hand serialization of this property to \\\n          Gson, which may or may not handle it.\n        \"\"\"\n          .trimIndent(),\n        Severity.INFORMATIONAL,\n      )\n\n    private val ISSUE_NON_MOSHI_CLASS_MAP =\n      createIssue(\n        \"NonMoshiClassMap\",\n        \"Concrete Map type '$HINT' is not natively supported by Moshi.\",\n        \"\"\"\n          The property type is concrete Map type (i.e. LinkedHashMap, HashMap, etc). Moshi only \\\n          natively supports their interface type (Map). Consider upcasting to the interface type. \\\n          Otherwise, moshi-gson-interop will hand serialization of this property to Gson, which may \\\n          or may not handle it.\n        \"\"\"\n          .trimIndent(),\n        Severity.INFORMATIONAL,\n      )\n\n    private val ISSUE_NON_MOSHI_CLASS_INTERNAL =\n      createIssue(\n        \"NonMoshiClassInternal\",\n        \"Non-Moshi internal type '$HINT' is not natively supported by Moshi.\",\n        \"\"\"\n          The property type is an internal type (i.e. slack.*) but is not a Moshi class itself. \\\n          moshi-gson-interop will hand serialization of this property to Gson, but consider \\\n          converting this type to Moshi as well to improve runtime performance and consistency.\n        \"\"\"\n          .trimIndent(),\n        Severity.INFORMATIONAL,\n      )\n\n    private val ISSUE_NON_MOSHI_CLASS_EXTERNAL =\n      createIssue(\n        \"NonMoshiClassExternal\",\n        \"External type '$HINT' is not natively supported by Moshi.\",\n        \"\"\"\n          The property type is an external type (i.e. not a Slack or built-in type). Moshi will try\n          to serialize these reflectively, which is not something we want. Either write a custom \\\n          adapter and annotating this property with `@AdaptedBy` or exclude/remove this type's use. \\\n          Otherwise, moshi-gson-interop will hand serialization of this property to Gson, which may \\\n          or may not handle it (also with reflection).\n        \"\"\"\n          .trimIndent(),\n      )\n\n    val ISSUE_ENUM_JSON_CLASS_GENERATED =\n      createIssue(\n        \"EnumJsonClassGenerated\",\n        \"Enums annotated with @JsonClass must not set `generateAdapter` to true.\",\n        \"\"\"\n          Enums annotated with @JsonClass do not need to set \"generateAdapter\" to true and should \\\n          set it to false.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    val ISSUE_ENUM_ANNOTATED_UNKNOWN =\n      createIssue(\n        \"EnumAnnotatedUnknown\",\n        \"UNKNOWN members in @JsonClass-annotated enums should not be annotated with @Json\",\n        \"\"\"\n          UNKNOWN members in @JsonClass-annotated enums should not be annotated with @Json. These \\\n          members are only used as a fallback and never expected in actual JSON bodies.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    val ISSUE_ENUM_UNKNOWN =\n      createIssue(\n        \"EnumMissingUnknown\",\n        \"Enums serialized with Moshi must reserve the first member as UNKNOWN.\",\n        \"\"\"\n          For backward compatibility, enums serialized with Moshi must reserve the first \\\n          member as \"UNKNOWN\". We will automatically substitute this when encountering \\\n          an unrecognized value for this enum during decoding.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    val ISSUE_ENUM_JSON_CLASS_MISSING =\n      createIssue(\n        \"EnumMissingJsonClass\",\n        \"Enums serialized with Moshi should be annotated with @JsonClass.\",\n        \"\"\"\n          This enum appears to use Moshi for serialization. Please also add an @JsonClass \\\n          annotation to it to ensure safe handling with unknown values and R8 optimization.\n        \"\"\"\n          .trimIndent(),\n      )\n\n    val ISSUE_ENUM_CASING =\n      createIssue(\n        \"EnumCasing\",\n        \"Consider using `@Json(name = ...)` rather than lower casing.\",\n        \"\"\"\n          Moshi offers `@Json` annotations to specify names to use in JSON serialization, similar \\\n          to Gson's `@SerializedName`. This can help avoid lower-casing enum properties in source \\\n          directly.\n        \"\"\"\n          .trimIndent(),\n        severity = Severity.WARNING,\n      )\n\n    // Please keep in alphabetical order for readability\n    fun issues(): List<Issue> =\n      listOf(\n        ISSUE_ADAPTED_BY_REQUIRES_ADAPTER,\n        ISSUE_ADAPTED_BY_REQUIRES_KEEP,\n        ISSUE_ARRAY,\n        ISSUE_BLANK_GENERATOR,\n        ISSUE_BLANK_JSON_NAME,\n        ISSUE_BLANK_TYPE_LABEL,\n        ISSUE_DOUBLE_CLASS_ANNOTATION,\n        ISSUE_DOUBLE_TYPE_LABEL,\n        ISSUE_DUPLICATE_JSON_NAME,\n        ISSUE_ENUM_ANNOTATED_UNKNOWN,\n        ISSUE_ENUM_CASING,\n        ISSUE_ENUM_JSON_CLASS_GENERATED,\n        ISSUE_ENUM_JSON_CLASS_MISSING,\n        ISSUE_ENUM_PROPERTY_COULD_BE_MOSHI,\n        ISSUE_ENUM_PROPERTY_DEFAULT_UNKNOWN,\n        ISSUE_ENUM_UNKNOWN,\n        ISSUE_GENERATE_ADAPTER_SHOULD_BE_TRUE,\n        ISSUE_GENERIC_SEALED_SUBTYPE,\n        ISSUE_INAPPROPRIATE_TYPE_LABEL,\n        ISSUE_JSON_SITE_TARGET,\n        ISSUE_MISSING_PRIMARY,\n        ISSUE_MISSING_TYPE_LABEL,\n        ISSUE_MUTABLE_COLLECTIONS,\n        ISSUE_QUALIFIER_RETENTION,\n        ISSUE_QUALIFIER_TARGET,\n        ISSUE_NON_MOSHI_CLASS_COLLECTION,\n        ISSUE_NON_MOSHI_CLASS_EXTERNAL,\n        ISSUE_NON_MOSHI_CLASS_INTERNAL,\n        ISSUE_NON_MOSHI_CLASS_MAP,\n        ISSUE_NON_MOSHI_CLASS_PLATFORM,\n        ISSUE_OBJECT,\n        ISSUE_PARAM_NEEDS_INIT,\n        ISSUE_PRIVATE_CONSTRUCTOR,\n        ISSUE_PRIVATE_PARAMETER,\n        ISSUE_REDUNDANT_JSON_NAME,\n        ISSUE_SEALED_MUST_BE_SEALED,\n        ISSUE_SERIALIZED_NAME,\n        ISSUE_SNAKE_CASE,\n        ISSUE_TRANSIENT_NEEDS_INIT,\n        ISSUE_UNSUPPORTED_TYPE,\n        ISSUE_USE_DATA,\n        ISSUE_VAR_PROPERTY,\n        ISSUE_VISIBILITY,\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/MustUseNamedParamsDetector.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.android.tools.lint.detector.api.isJava\nimport org.jetbrains.kotlin.psi.KtValueArgument\nimport org.jetbrains.kotlin.psi.KtValueArgumentList\nimport org.jetbrains.kotlin.psi.KtValueArgumentName\nimport org.jetbrains.kotlin.psi.psiUtil.getChildOfType\nimport org.jetbrains.uast.UCallExpression\nimport slack.lint.util.sourceImplementation\n\nclass MustUseNamedParamsDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitCallExpression(node: UCallExpression) {\n        val method = node.resolve() ?: return\n\n        // Java doesn't have named parameters.\n        if (isJava(method.language)) return\n\n        if (method.hasAnnotation(\"slack.lint.annotations.MustUseNamedParams\")) {\n          val areAllNamed =\n            node.sourcePsi!!\n              .getChildOfType<KtValueArgumentList>()!!\n              .children\n              .filterIsInstance<KtValueArgument>()\n              .all { it.getChildOfType<KtValueArgumentName>() != null }\n\n          if (!areAllNamed) {\n            context.report(\n              ISSUE,\n              node,\n              context.getLocation(node),\n              ISSUE.getBriefDescription(TextFormat.TEXT),\n            )\n          }\n        }\n      }\n    }\n  }\n\n  companion object {\n    val ISSUE: Issue =\n      Issue.create(\n        \"MustUseNamedParams\",\n        \"Calls to @MustUseNamedParams-annotated methods must name all parameters.\",\n        \"Calls to @MustUseNamedParams-annotated methods must name all parameters.\",\n        Category.CORRECTNESS,\n        9,\n        Severity.ERROR,\n        sourceImplementation<MustUseNamedParamsDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/NonKotlinPairDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport java.util.EnumSet\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.getContainingUClass\nimport org.jetbrains.uast.resolveToUElement\nimport org.jetbrains.uast.util.isConstructorCall\n\n/**\n * Checks to make sure that the code base will use the [kotlin.Pair] instead of any other\n * alternative Pairs. Pairs might come from other APIs, so this Detector is only concerned about\n * creating new Pairs.\n *\n * Cases that this detector should be warning about from the Java (and Kotlin) perspective.\n * - new androidx.core.util.Pair()\n * - androidx.core.util.Pair.create()\n * - new slack.commons.Pair()\n * - new android.util.Pair()\n */\nclass NonKotlinPairDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> {\n    return listOf(UCallExpression::class.java)\n  }\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitCallExpression(node: UCallExpression) {\n        when {\n          node.isConstructorCall() -> {\n            checkForBannedPairType(context, node, node.resolveToUElement()?.getContainingUClass())\n          }\n          node.methodName == METHOD_NAME_PAIR_CREATE -> {\n            val resolved = node.resolveToUElement() ?: return\n            if (resolved is UMethod && resolved.isStatic) {\n              checkForBannedPairType(context, node, resolved.getContainingUClass())\n            }\n          }\n        }\n      }\n    }\n  }\n\n  private fun checkForBannedPairType(context: JavaContext, node: UCallExpression, uClass: UClass?) {\n    if (uClass?.qualifiedName in BANNED_PAIR_TYPES) {\n      val location = context.getLocation(node)\n      val issueToReport = ISSUE_KOTLIN_PAIR_NOT_CREATED\n      context.report(\n        issueToReport,\n        location,\n        issueToReport.getBriefDescription(TextFormat.TEXT),\n        null,\n      )\n    }\n  }\n\n  companion object {\n    private const val FQN_KOTLIN_PAIR = \"kotlin.Pair\"\n    private const val FQN_SLACK_COMMONS_PAIR = \"slack.commons.Pair\"\n    private const val FQN_ANDROIDX_PAIR = \"androidx.core.util.Pair\"\n    private const val FQN_ANDROID_DEPRECATED_PAIR = \"android.util.Pair\"\n    private const val FQN_ANDROID_PAIR = \"com.android.utils\"\n    private const val METHOD_NAME_PAIR_CREATE = \"create\"\n\n    private val BANNED_PAIR_TYPES =\n      listOf(\n        FQN_SLACK_COMMONS_PAIR,\n        FQN_ANDROIDX_PAIR,\n        FQN_ANDROID_DEPRECATED_PAIR,\n        FQN_ANDROID_PAIR,\n      )\n\n    /** Scope-set used for detectors which are affected by a single Java source file */\n    private val JAVA_FILE_AND_TEST_SOURCES_SCOPE: EnumSet<Scope> =\n      EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)\n\n    private val ISSUE_KOTLIN_PAIR_NOT_CREATED: Issue =\n      Issue.create(\n        \"KotlinPairNotCreated\",\n        \"Use Kotlin's $FQN_KOTLIN_PAIR instead of other Pair types from other libraries like AndroidX and Slack commons\",\n        \"\"\"\n          We should consolidate to create and use a single type of Pair in the code base. Kotlin's Pair is preferred as it \\\n          works well with Java and Kotlin. It comes with extension functions that Slack's Pair doesn't offer.\n      \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.WARNING,\n        Implementation(NonKotlinPairDetector::class.java, JAVA_FILE_AND_TEST_SOURCES_SCOPE),\n      )\n\n    val issues: List<Issue> = listOf(ISSUE_KOTLIN_PAIR_NOT_CREATED)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/NotNullOperatorDetector.kt",
    "content": "// Copyright (C) 2024 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport kotlin.jvm.java\nimport org.jetbrains.uast.UPostfixExpression\nimport org.jetbrains.uast.kotlin.isKotlin\nimport slack.lint.util.sourceImplementation\n\nclass NotNullOperatorDetector : Detector(), SourceCodeScanner {\n  override fun getApplicableUastTypes() = listOf(UPostfixExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    if (!isKotlin(context.uastFile?.lang)) return null\n\n    return object : UElementHandler() {\n      override fun visitPostfixExpression(node: UPostfixExpression) {\n        if (node.operator.text == \"!!\") {\n          // Report a warning for the usage of `!!`\n          context.report(\n            ISSUE,\n            node,\n            context.getLocation(node.operatorIdentifier ?: node),\n            \"Avoid using the `!!` operator\",\n          )\n        }\n      }\n    }\n  }\n\n  companion object {\n    val ISSUE: Issue =\n      Issue.create(\n        \"AvoidUsingNotNullOperator\",\n        \"Avoid using the !! operator in Kotlin\",\n        \"\"\"\n        The `!!` operator is a not-null assertion in Kotlin that will lead to a \\\n        `NullPointerException` if the value is null. It's better to use safe \\\n        null-handling mechanisms like `?.`, `?:`, `?.let`, etc.\n        \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.WARNING,\n        sourceImplementation<NotNullOperatorDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/NullableConcurrentHashMapDetector.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Location\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.intellij.psi.PsiNewExpression\nimport com.intellij.psi.PsiReferenceParameterList\nimport com.intellij.psi.PsiTypeElement\nimport org.jetbrains.kotlin.psi.KtCallExpression\nimport org.jetbrains.kotlin.psi.KtNullableType\nimport org.jetbrains.kotlin.psi.KtTypeReference\nimport org.jetbrains.kotlin.psi.psiUtil.getChildOfType\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UVariable\nimport org.jetbrains.uast.getContainingUClass\nimport org.jetbrains.uast.resolveToUElement\nimport org.jetbrains.uast.util.isConstructorCall\nimport slack.lint.util.sourceImplementation\n\nclass NullableConcurrentHashMapDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java, UVariable::class.java)\n\n  override fun createUastHandler(context: JavaContext) =\n    object : UElementHandler() {\n      override fun visitVariable(node: UVariable) {\n        if (node.typeReference?.getQualifiedName() != CONCURRENT_HASH_MAP) return\n\n        // Check if key type is nullable\n        node.check(0, \"key\")\n\n        // Check if value type is nullable\n        node.check(1, \"value\")\n      }\n\n      private fun UVariable.check(typeArgIndex: Int, name: String) {\n        val location =\n          when (val typeRefPsi = typeReference?.sourcePsi) {\n            is KtTypeReference -> {\n              typeRefPsi.typeElement\n                ?.typeArgumentsAsTypes\n                ?.takeIf { it.size == 2 }\n                ?.getOrNull(typeArgIndex)\n                ?.typeElement\n                ?.takeIf { it is KtNullableType }\n                ?.let(context::getLocation)\n            }\n            is PsiTypeElement -> {\n              typeRefPsi.innermostComponentReferenceElement\n                ?.getChildOfType<PsiReferenceParameterList>()\n                ?.takeIf { it.typeParameterElements.size == 2 }\n                ?.obtainIfNullable(typeArgIndex)\n            }\n            else -> null\n          }\n\n        if (location != null) {\n          context.report(ISSUE, location, \"ConcurrentHashMap should not use nullable $name types\")\n        }\n      }\n\n      override fun visitCallExpression(node: UCallExpression) {\n        // Check if it's a constructor call\n        if (!node.isConstructorCall()) return\n\n        // Get the class being constructed\n        val uClass = node.resolveToUElement()?.getContainingUClass() ?: return\n\n        // Check if it's a ConcurrentHashMap\n        if (uClass.qualifiedName != CONCURRENT_HASH_MAP) return\n\n        // Check if key type is nullable\n        node.check(0, \"key\")\n\n        // Check if value type is nullable\n        node.check(1, \"value\")\n      }\n\n      private fun UCallExpression.check(typeArgIndex: Int, name: String) {\n        val location =\n          when (val sourcePsi = sourcePsi) {\n            is KtCallExpression -> {\n              val typeElement =\n                sourcePsi.typeArguments.getOrNull(typeArgIndex)?.typeReference?.typeElement\n                  ?: return\n\n              val isNullable = typeElement is KtNullableType\n              if (isNullable) {\n                context.getLocation(typeElement)\n              } else {\n                null\n              }\n            }\n            is PsiNewExpression -> {\n              sourcePsi.classReference\n                ?.getChildOfType<PsiReferenceParameterList>()\n                ?.takeIf { it.typeParameterElements.size == 2 }\n                ?.obtainIfNullable(typeArgIndex)\n            }\n            else -> null\n          }\n\n        if (location != null) {\n          context.report(ISSUE, location, \"ConcurrentHashMap should not use nullable $name types\")\n        }\n      }\n\n      private fun PsiReferenceParameterList.obtainIfNullable(typeArgIndex: Int): Location? {\n        val typeArg = typeParameterElements.getOrNull(typeArgIndex) ?: return null\n        val isNullable =\n          typeArg.type.annotations.any { it.qualifiedName?.endsWith(\".Nullable\") == true }\n\n        return if (isNullable) {\n          context.getLocation(typeArg)\n        } else {\n          null\n        }\n      }\n    }\n\n  companion object {\n    private const val CONCURRENT_HASH_MAP = \"java.util.concurrent.ConcurrentHashMap\"\n\n    val ISSUE =\n      Issue.create(\n        id = \"NullableConcurrentHashMap\",\n        briefDescription = \"ConcurrentHashMap should not use nullable types\",\n        explanation =\n          \"\"\"\n        ConcurrentHashMap does not support null keys or values. \\\n        Use non-nullable types for both keys and values when creating a ConcurrentHashMap.\n      \"\"\",\n        category = Category.CORRECTNESS,\n        priority = 5,\n        severity = Severity.ERROR,\n        implementation = sourceImplementation<NullableConcurrentHashMapDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/RawDispatchersUsageDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.android.tools.lint.detector.api.UastLintUtils.Companion.tryResolveUDeclaration\nimport com.intellij.psi.PsiClass\nimport com.intellij.psi.PsiClassType\nimport java.util.EnumSet\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UCallableReferenceExpression\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.UQualifiedReferenceExpression\nimport org.jetbrains.uast.kotlin.isKotlin\n\n/**\n * This is a [Detector] for detecting direct usages of Kotlin coroutines'\n * [kotlinx.coroutines.Dispatchers] properties, which we want to prevent in favor of our\n * `SlackDispatchers` abstraction.\n */\nclass RawDispatchersUsageDetector : Detector(), SourceCodeScanner {\n\n  companion object {\n    private val SCOPES =\n      Implementation(RawDispatchersUsageDetector::class.java, EnumSet.of(Scope.JAVA_FILE))\n\n    val ISSUE: Issue =\n      Issue.create(\n        \"RawDispatchersUse\",\n        \"Use SlackDispatchers.\",\n        \"\"\"\n        Direct use of `Dispatchers.*` APIs are discouraged as they are difficult to test. Prefer using \\\n        `SlackDispatchers`.\n      \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        SCOPES,\n      )\n\n    private const val DISPATCHERS_CLASS = \"kotlinx.coroutines.Dispatchers\"\n    private val PROPERTY_GETTERS =\n      setOf(\n        \"getDefault\",\n        \"getIO\",\n        \"getMain\",\n        \"getUnconfined\",\n        \"Default\",\n        \"IO\",\n        \"Main\",\n        \"Unconfined\",\n      )\n  }\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> =\n    listOf(\n      UCallExpression::class.java,\n      UCallableReferenceExpression::class.java,\n      UQualifiedReferenceExpression::class.java,\n    )\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    // Only applicable on Kotlin files\n    if (!isKotlin(context.uastFile?.lang)) return null\n\n    fun report(node: UElement) {\n      context.report(ISSUE, context.getLocation(node), ISSUE.getBriefDescription(TextFormat.TEXT))\n    }\n\n    fun String?.isDispatcherGetter(): Boolean {\n      return this in PROPERTY_GETTERS\n    }\n\n    fun PsiClass?.isDispatchersClass(): Boolean {\n      if (this == null) return false\n      return qualifiedName == DISPATCHERS_CLASS\n    }\n\n    return object : UElementHandler() {\n      // Awkward but this is how to look for property getter calls\n      override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {\n        val expressionType = node.receiver.getExpressionType() ?: return\n        if (expressionType is PsiClassType && expressionType.resolve().isDispatchersClass()) {\n          val uDecl = node.selector.tryResolveUDeclaration() ?: return\n          if (uDecl is UMethod && uDecl.name.isDispatcherGetter()) {\n            report(node)\n          }\n        }\n      }\n\n      override fun visitCallExpression(node: UCallExpression) {\n        if (node.methodName.isDispatcherGetter()) {\n          val resolved = node.resolve() ?: return\n          if (resolved.containingClass.isDispatchersClass()) {\n            report(node)\n          }\n        }\n      }\n\n      override fun visitCallableReferenceExpression(node: UCallableReferenceExpression) {\n        if (node.callableName.isDispatcherGetter()) {\n          val qualifierType = node.qualifierType ?: return\n          if (qualifierType is PsiClassType && qualifierType.resolve().isDispatchersClass())\n            report(node)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/RedactedUsageDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport org.jetbrains.uast.UAnnotated\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UField\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.kotlin.isKotlin\nimport slack.lint.util.sourceImplementation\n\n/**\n * A simple detector that ensures that `@Redacted` annotations are only used in Kotlin files (Java\n * is unsupported).\n */\n// TODO check for toString() impls in Kotlin classes using Redacted? i.e. it's an error to have both\n// TODO check that redacted classes are data classes\nclass RedactedUsageDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> {\n    return listOf(UClass::class.java, UMethod::class.java, UField::class.java)\n  }\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    // Redacted can only be used in Kotlin files, so this check only checks in Java files\n    if (isKotlin(context.uastFile?.lang)) return null\n\n    return object : UElementHandler() {\n      override fun visitClass(node: UClass) = node.checkRedacted()\n\n      override fun visitField(node: UField) = node.checkRedacted()\n\n      override fun visitMethod(node: UMethod) = node.checkRedacted()\n\n      fun UAnnotated.checkRedacted() {\n        uAnnotations\n          .find { it.qualifiedName?.contains(NAME_REDACTED, ignoreCase = true) == true }\n          ?.let { redactedAnnotation ->\n            context.report(\n              JAVA_USAGE,\n              context.getLocation(redactedAnnotation),\n              JAVA_USAGE.getBriefDescription(TextFormat.TEXT),\n              quickfixData = null,\n            )\n          }\n      }\n    }\n  }\n\n  companion object {\n    // We check simple name only rather than any specific redacted annotation\n    private const val NAME_REDACTED = \"redacted\"\n\n    private val JAVA_USAGE: Issue =\n      Issue.create(\n        \"RedactedInJavaUsage\",\n        \"@Redacted is only supported in Kotlin classes!\",\n        \"@Redacted is only supported in Kotlin classes!\",\n        Category.CORRECTNESS,\n        9,\n        Severity.ERROR,\n        sourceImplementation<RedactedUsageDetector>(),\n      )\n\n    val ISSUES = listOf(JAVA_USAGE)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/RestrictCallsToDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UFile\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.getContainingUFile\nimport org.jetbrains.uast.resolveToUElement\nimport org.jetbrains.uast.toUElementOfType\nimport slack.lint.util.sourceImplementation\n\nclass RestrictCallsToDetector : Detector(), SourceCodeScanner {\n\n  companion object {\n    val ISSUE: Issue =\n      Issue.create(\n        \"RestrictCallsTo\",\n        \"Methods annotated with @RestrictedCallsTo should only be called from the specified scope.\",\n        \"\"\"\n          This method is intended to only be called from the specified scope despite it being \\\n          public. This could be due to its use in an interface or similar. Overrides are still \\\n          ok.\n      \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<RestrictCallsToDetector>(),\n      )\n\n    private const val RESTRICT_CALLS_TO_ANNOTATION = \"slack.lint.annotations.RestrictCallsTo\"\n  }\n\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext) =\n    object : UElementHandler() {\n\n      @Suppress(\"UNUSED_VARIABLE\") // Toe-hold for now until we support other scopes\n      override fun visitCallExpression(node: UCallExpression) {\n        val method = node.resolveToUElement() as? UMethod ?: return\n\n        val (restrictCallsTo, annotatedMethod) =\n          method\n            .superMethodSequence()\n            .mapNotNull { superMethod ->\n              superMethod.findAnnotation(RESTRICT_CALLS_TO_ANNOTATION)?.let { it to superMethod }\n            }\n            .firstOrNull() ?: return\n\n        val containingFile = annotatedMethod.getContainingUFile() ?: return\n        val callingFile = node.getContainingUFile() ?: return\n        if (!callingFile.isSameAs(containingFile)) {\n          context.report(\n            ISSUE,\n            node,\n            context.getLocation(node),\n            ISSUE.getBriefDescription(TextFormat.TEXT),\n          )\n        }\n      }\n\n      private fun UMethod.superMethodSequence(): Sequence<UMethod> {\n        return generateSequence(this) {\n          if (context.evaluator.isOverride(it)) {\n            // Note this doesn't try to check multiple interfaces, but we can fix that in the future\n            // if it matters\n            it.findSuperMethods()[0].toUElementOfType()\n          } else {\n            null\n          }\n        }\n      }\n    }\n\n  // UFile isn't inherently comparable, so package and simple name are close enough for us.\n  private fun UFile.isSameAs(other: UFile): Boolean {\n    return this.packageName == other.packageName && this.sourcePsi.name == other.sourcePsi.name\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/SerializableDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport org.jetbrains.uast.UClass\nimport slack.lint.util.implements\nimport slack.lint.util.sourceImplementation\n\nclass SerializableDetector : Detector(), SourceCodeScanner {\n  override fun getApplicableUastTypes() = listOf(UClass::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitClass(node: UClass) {\n        if (node.isEnum) return\n        val implementsSerializable =\n          node.implements(\"java.io.Serializable\") { fqcn ->\n            // Only look in slack sources\n            \"slack\" in fqcn\n          }\n        if (implementsSerializable) {\n          // TODO after we drop Serializable entirely, we should always make it an error.\n          if (node.implements(\"android.os.Parcelable\")) return\n\n          context.report(\n            ISSUE,\n            context.getNameLocation(node),\n            ISSUE.getBriefDescription(TextFormat.TEXT),\n          )\n        }\n      }\n    }\n  }\n\n  companion object {\n    val ISSUE: Issue =\n      Issue.create(\n        \"SerializableUsage\",\n        \"Don't use Serializable.\",\n        \"\"\"\n        Don't use Serializable. It's brittle, requires reflection, does not \\\n        work well with Kotlin, and prevents us from using Core Library Desugaring. \\\n        Either implement Parcelable too or use another safer serialization mechanism.\n      \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<SerializableDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/SlackIssueRegistry.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.IssueRegistry\nimport com.android.tools.lint.client.api.Vendor\nimport com.android.tools.lint.detector.api.CURRENT_API\nimport com.android.tools.lint.detector.api.Issue\nimport com.google.auto.service.AutoService\nimport slack.lint.denylistedapis.DenyListedApiDetector\nimport slack.lint.eithernet.DoNotExposeEitherNetInRepositoriesDetector\nimport slack.lint.inclusive.InclusiveNamingChecker\nimport slack.lint.mocking.ErrorProneDoNotMockDetector\nimport slack.lint.mocking.MockDetector\nimport slack.lint.parcel.ParcelizeFunctionPropertyDetector\nimport slack.lint.resources.FullyQualifiedResourceDetector\nimport slack.lint.resources.MissingResourceImportAliasDetector\nimport slack.lint.resources.WrongResourceImportAliasDetector\nimport slack.lint.retrofit.RetrofitUsageDetector\nimport slack.lint.rx.RxObservableEmitDetector\nimport slack.lint.rx.RxSubscribeOnMainDetector\nimport slack.lint.text.SpanMarkPointMissingMaskDetector\nimport slack.lint.ui.DoNotCallViewToString\nimport slack.lint.ui.ItemDecorationViewBindingDetector\n\n@AutoService(IssueRegistry::class)\nclass SlackIssueRegistry : IssueRegistry() {\n\n  override val vendor: Vendor =\n    Vendor(\n      vendorName = \"slack\",\n      identifier = \"slack-lint\",\n      feedbackUrl = \"https://github.com/slackhq/slack-lints\",\n      contact = \"https://github.com/slackhq/slack-lints\",\n    )\n\n  override val api: Int = CURRENT_API\n  override val minApi: Int = CURRENT_API\n\n  override val issues: List<Issue> = buildList {\n    addAll(ViewContextDetector.issues)\n    addAll(ArgInFormattedQuantityStringResDetector.issues)\n    addAll(DaggerIssuesDetector.ISSUES)\n    addAll(NonKotlinPairDetector.issues)\n    add(DoNotCallProvidersDetector.ISSUE)\n    addAll(InclusiveNamingChecker.ISSUES)\n    add(DeprecatedAnnotationDetector.ISSUE_DEPRECATED_CALL)\n    add(DeprecatedSqlUsageDetector.ISSUE)\n    add(JavaOnlyDetector.ISSUE)\n    add(SerializableDetector.ISSUE)\n    add(RawDispatchersUsageDetector.ISSUE)\n    add(MainScopeUsageDetector.ISSUE)\n    add(RxSubscribeOnMainDetector.ISSUE)\n    addAll(RxObservableEmitDetector.issues)\n    addAll(GuavaPreconditionsDetector.issues)\n    addAll(MockDetector.ALL_ISSUES)\n    add(ErrorProneDoNotMockDetector.ISSUE)\n    addAll(MoshiUsageDetector.issues())\n    addAll(FragmentDaggerFieldInjectionDetector.issues)\n    addAll(RedactedUsageDetector.ISSUES)\n    add(InjectInJavaDetector.ISSUE)\n    add(RetrofitUsageDetector.ISSUE)\n    add(RestrictCallsToDetector.ISSUE)\n    add(SpanMarkPointMissingMaskDetector.ISSUE)\n    add(DoNotExposeEitherNetInRepositoriesDetector.ISSUE)\n    add(FullyQualifiedResourceDetector.ISSUE)\n    add(MissingResourceImportAliasDetector.ISSUE)\n    add(WrongResourceImportAliasDetector.ISSUE)\n    addAll(DenyListedApiDetector.ISSUES)\n    add(ParcelizeFunctionPropertyDetector.ISSUE)\n    add(ExceptionMessageDetector.ISSUE)\n    add(TestParameterSiteTargetDetector.ISSUE)\n    add(MustUseNamedParamsDetector.ISSUE)\n    add(NotNullOperatorDetector.ISSUE)\n    add(DoNotCallViewToString.ISSUE)\n    add(ItemDecorationViewBindingDetector.ISSUE)\n    add(NullableConcurrentHashMapDetector.ISSUE)\n    add(CircuitScreenDataClassDetector.ISSUE)\n    add(JsonInflaterMoshiCompatibilityDetector.ISSUE)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/TestParameterSiteTargetDetector.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport org.jetbrains.kotlin.psi.KtParameter\nimport org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter\nimport org.jetbrains.uast.UAnnotation\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.UParameter\nimport org.jetbrains.uast.kotlin.isKotlin\nimport org.jetbrains.uast.toUElementOfType\nimport slack.lint.util.sourceImplementation\n\n/**\n * Detector that checks for `@TestParameter` annotations on parameter properties and ensures they\n * have param: site targets.\n */\nclass TestParameterSiteTargetDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> {\n    return listOf(UMethod::class.java)\n  }\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    // Only check Kotlin files\n    if (!isKotlin(context.uastFile?.lang)) return null\n\n    return object : UElementHandler() {\n      override fun visitMethod(node: UMethod) {\n        if (!node.isConstructor) return\n\n        // Check parameters\n        for (parameter in node.uastParameters) {\n          checkParameter(parameter)\n        }\n      }\n\n      private fun checkParameter(parameter: UParameter) {\n        val sourcePsi = parameter.sourcePsi as? KtParameter ?: return\n\n        // Only check property parameters\n        if (!sourcePsi.isPropertyParameter()) return\n\n        // Find @TestParameter annotations\n        for (annotationEntry in sourcePsi.annotationEntries) {\n          val qualifiedName = (annotationEntry.toUElementOfType<UAnnotation>())?.qualifiedName\n\n          // Check if this is a TestParameter annotation\n          if (\n            qualifiedName == TEST_PARAMETER_ANNOTATION ||\n              annotationEntry.shortName?.asString() == \"TestParameter\"\n          ) {\n\n            // Check if the annotation has a param: site target\n            if (annotationEntry.useSiteTarget?.text != \"param\") {\n              // Get the full text of the annotation\n              val annotationText = annotationEntry.text\n\n              // Create the replacement text by adding or replacing the site target\n              val replacementText =\n                if (annotationEntry.useSiteTarget != null) {\n                  // Replace existing site target with param:\n                  annotationText.replace(\"${annotationEntry.useSiteTarget!!.text}:\", \"param:\")\n                } else {\n                  // Add param: site target\n                  annotationText.replace(\"@\", \"@param:\")\n                }\n\n              context.report(\n                ISSUE,\n                context.getLocation(annotationEntry),\n                ISSUE.getBriefDescription(TextFormat.TEXT),\n                LintFix.create().replace().text(annotationText).with(replacementText).build(),\n              )\n            }\n          }\n        }\n      }\n    }\n  }\n\n  companion object {\n    private const val TEST_PARAMETER_ANNOTATION =\n      \"com.google.testing.junit.testparameterinjector.TestParameter\"\n\n    val ISSUE =\n      Issue.create(\n        id = \"TestParameterSiteTarget\",\n        briefDescription = \"`TestParameter` annotation has the wrong site target\",\n        explanation =\n          \"\"\"\n        `TestParameter` annotations on parameter properties must have `param:` site targets.\\\n        \\\n        For example:\\\n        ```kotlin\\\n        class MyTest(\\\n          @param:TestParameter val myParam: String\\\n        )\\\n        ```\\\n        \\\n        For more information, see: https://github.com/google/TestParameterInjector/issues/49\n      \"\"\",\n        category = Category.CORRECTNESS,\n        priority = 5,\n        severity = Severity.ERROR,\n        implementation = sourceImplementation<TestParameterSiteTargetDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/ViewContextDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.detector.api.Category.Companion.CORRECTNESS\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.intellij.psi.PsiMethod\nimport com.intellij.psi.impl.compiled.ClsClassImpl\nimport org.jetbrains.uast.UBinaryExpressionWithType\nimport org.jetbrains.uast.UCallExpression\nimport slack.lint.util.sourceImplementation\n\n/**\n * This [Detector] scans Java files for calls to methods named \"getContext\". It then checks if the\n * object being called is an `android.view.View` and if they are casting the returned\n * `android.content.Context` to `android.app.Activity`. If so, an [Issue] with id\n * `CastingViewContextToActivity` is reported.\n */\n@Suppress(\"UnstableApiUsage\")\nclass ViewContextDetector : Detector(), SourceCodeScanner {\n  override fun getApplicableMethodNames(): List<String> {\n    return listOf(\"getContext\")\n  }\n\n  override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {\n    if (\n      method.parent is ClsClassImpl &&\n        \"android.view.View\" == (method.parent as ClsClassImpl).qualifiedName\n    ) {\n      // Check if the parent expression is a cast expression\n      var parent = node.uastParent\n      if (parent != null) {\n        // Check if it's called as 'view.getContext()'\n        if (parent.uastParent is UBinaryExpressionWithType) {\n          parent = parent.uastParent\n        }\n        if (parent is UBinaryExpressionWithType) {\n          // See if it is being cast to android.App.Activity\n          if (\"android.app.Activity\" == parent.type.canonicalText) {\n            // report an issue\n            context.report(\n              issue = ISSUE_VIEW_CONTEXT_CAST,\n              scope = node,\n              location = context.getLocation(parent),\n              message = ISSUE_VIEW_CONTEXT_CAST.getBriefDescription(TextFormat.TEXT),\n            )\n          }\n        }\n      }\n    }\n  }\n\n  companion object {\n    val ISSUE_VIEW_CONTEXT_CAST: Issue =\n      Issue.create(\n        \"CastingViewContextToActivity\",\n        \"Unsafe cast of `Context` to `Activity`\",\n        \"\"\"`View.getContext()` is not guaranteed to return an `Activity` and can often \\\n        return a `ContextWrapper` instead resulting in a `ClassCastException`. Instead, use \\\n        `UiUtils.getActivityFromView()`.\n        \"\"\",\n        CORRECTNESS,\n        9,\n        Severity.ERROR,\n        sourceImplementation<ViewContextDetector>(),\n      )\n\n    val issues: List<Issue>\n      get() = listOf(ISSUE_VIEW_CONTEXT_CAST)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/denylistedapis/DenyListedApiDetector.kt",
    "content": "/*\nCopyright 2022 Square, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n   http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage slack.lint.denylistedapis\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category.Companion.CORRECTNESS\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LocationType.NAME\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.Severity.ERROR\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.XmlContext\nimport com.android.tools.lint.detector.api.XmlScanner\nimport com.intellij.psi.PsiField\nimport com.intellij.psi.PsiMethod\nimport java.util.EnumSet\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UExpression\nimport org.jetbrains.uast.UImportStatement\nimport org.jetbrains.uast.ULiteralExpression\nimport org.jetbrains.uast.UQualifiedReferenceExpression\nimport org.jetbrains.uast.util.isConstructorCall\nimport org.w3c.dom.Element\nimport slack.lint.denylistedapis.DenyListedEntry.Companion.MatchAll\n\n/**\n * Deny-listed APIs that we don't want people to use.\n *\n * Adapted from https://gist.github.com/JakeWharton/1f102d98cd10133b03a5f374540c327a\n */\ninternal class DenyListedApiDetector : Detector(), SourceCodeScanner, XmlScanner {\n\n  override fun getApplicableUastTypes() = CONFIG.applicableTypes()\n\n  override fun createUastHandler(context: JavaContext) = CONFIG.visitor(context)\n\n  override fun getApplicableElements() = CONFIG.applicableLayoutInflaterElements.keys\n\n  override fun visitElement(context: XmlContext, element: Element) =\n    CONFIG.visitor(context, element)\n\n  private class DenyListConfig(vararg entries: DenyListedEntry) {\n    private class TypeConfig(entries: List<DenyListedEntry>) {\n      @Suppress(\"UNCHECKED_CAST\") // Safe because of filter call.\n      val functionEntries =\n        entries.groupBy { it.functionName }.filterKeys { it != null }\n          as Map<String, List<DenyListedEntry>>\n\n      @Suppress(\"UNCHECKED_CAST\") // Safe because of filter call.\n      val referenceEntries =\n        entries.groupBy { it.fieldName }.filterKeys { it != null }\n          as Map<String, List<DenyListedEntry>>\n    }\n\n    val issues = entries.asSequence().map { it.issue }.distinctBy { it.id }.toList()\n\n    private val typeConfigs =\n      entries.groupBy { it.className }.mapValues { (_, entries) -> TypeConfig(entries) }\n\n    val applicableLayoutInflaterElements =\n      entries\n        .filter { it.functionName == \"<init>\" }\n        .filter {\n          it.arguments == null ||\n            it.arguments == listOf(\"android.content.Context\", \"android.util.AttributeSet\")\n        }\n        .groupBy { it.className }\n        .mapValues { (cls, entries) ->\n          entries.singleOrNull() ?: error(\"Multiple two-arg init rules for $cls\")\n        }\n\n    fun applicableTypes() =\n      listOf<Class<out UElement>>(\n        UCallExpression::class.java,\n        UImportStatement::class.java,\n        UQualifiedReferenceExpression::class.java,\n      )\n\n    fun visitor(context: JavaContext) =\n      object : UElementHandler() {\n        override fun visitCallExpression(node: UCallExpression) {\n          val function = node.resolve() ?: return\n\n          val className = function.containingClass?.qualifiedName\n          val typeConfig = typeConfigs[className] ?: return\n\n          val functionName =\n            if (node.isConstructorCall()) {\n              \"<init>\"\n            } else {\n              // Kotlin compiler mangles function names that use inline value types as parameters by\n              // suffixing them\n              // with a hyphen.\n              // https://github.com/Kotlin/KEEP/blob/master/proposals/inline-classes.md#mangling-rules\n              function.name.substringBefore(\"-\")\n            }\n\n          val deniedFunctions =\n            typeConfig.functionEntries.getOrDefault(functionName, emptyList()) +\n              typeConfig.functionEntries.getOrDefault(MatchAll, emptyList())\n\n          deniedFunctions.forEach { denyListEntry ->\n            if (denyListEntry.allowInTests && context.isTestSource) {\n              return@forEach\n            } else if (\n              denyListEntry.parametersMatchWith(function) && denyListEntry.argumentsMatchWith(node)\n            ) {\n              context.report(\n                issue = denyListEntry.issue,\n                location = context.getNameLocation(node),\n                message = denyListEntry.errorMessage,\n              )\n            }\n          }\n        }\n\n        override fun visitImportStatement(node: UImportStatement) {\n          val reference = node.resolve() as? PsiField ?: return\n          visitField(reference, node)\n        }\n\n        override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {\n          val reference = node.resolve() as? PsiField ?: return\n          visitField(reference, node)\n        }\n\n        private fun visitField(reference: PsiField, node: UElement) {\n          val className = reference.containingClass?.qualifiedName\n          val typeConfig = typeConfigs[className] ?: return\n\n          val referenceName = reference.name\n          val deniedFunctions =\n            typeConfig.referenceEntries.getOrDefault(referenceName, emptyList()) +\n              typeConfig.referenceEntries.getOrDefault(MatchAll, emptyList())\n\n          deniedFunctions.forEach { denyListEntry ->\n            if (denyListEntry.allowInTests && context.isTestSource) {\n              return@forEach\n            }\n            context.report(\n              issue = denyListEntry.issue,\n              location = context.getLocation(node),\n              message = denyListEntry.errorMessage,\n            )\n          }\n        }\n      }\n\n    fun visitor(context: XmlContext, element: Element) {\n      val denyListEntry = applicableLayoutInflaterElements.getValue(element.tagName)\n      context.report(\n        issue = denyListEntry.issue,\n        location = context.getLocation(element, type = NAME),\n        message = denyListEntry.errorMessage,\n      )\n    }\n\n    private fun DenyListedEntry.parametersMatchWith(function: PsiMethod): Boolean {\n      val expected = parameters\n      val actual = function.parameterList.parameters.map { it.type.canonicalText }\n\n      return when {\n        expected == null -> true\n        expected.isEmpty() && actual.isEmpty() -> true\n        expected.size != actual.size -> false\n        else -> expected == actual\n      }\n    }\n\n    private fun DenyListedEntry.argumentsMatchWith(node: UCallExpression): Boolean {\n      // \"arguments\" being null means we don't care about this check and it should just return true.\n      val expected = arguments ?: return true\n      val actual = node.valueArguments\n\n      return when {\n        expected.size != actual.size -> false\n        else ->\n          expected.zip(actual).all { (expectedValue, actualValue) ->\n            argumentMatches(expectedValue, actualValue)\n          }\n      }\n    }\n\n    private fun argumentMatches(expectedValue: String, actualValue: UExpression): Boolean {\n      if (expectedValue == \"*\") return true\n      val renderString =\n        (actualValue as? ULiteralExpression)?.asRenderString()\n          ?: (actualValue as? UQualifiedReferenceExpression)\n            ?.asRenderString() // Helps to match against static method params\n      // 'Class.staticMethod()'.\n      if (expectedValue == renderString) return true\n\n      return false\n    }\n  }\n\n  companion object {\n    val DEFAULT_ISSUE = createIssue(\"DenyListedApi\")\n    val BLOCKING_ISSUE = createIssue(\"DenyListedBlockingApi\")\n\n    private val CONFIG =\n      DenyListConfig(\n        DenyListedEntry(\n          className = \"io.reactivex.rxjava3.core.Observable\",\n          functionName = \"hide\",\n          errorMessage =\n            \"There should be no reason to defend against downcasting an Observable to \" +\n              \"an implementation type like Relay or Subject in a closed codebase. Doing this incurs \" +\n              \"needless runtime memory and performance overhead. Relays and Subjects both extend from \" +\n              \"Observable and can be supplied to functions accepting Observable directly. When \" +\n              \"returning a Relay or Subject, declare the return type explicitly as Observable \" +\n              \"(e.g., fun foo(): Observable<Foo> = fooRelay).\",\n        ),\n        DenyListedEntry(\n          className = \"io.reactivex.rxjava3.core.Flowable\",\n          functionName = \"hide\",\n          errorMessage =\n            \"There should be no reason to defend against downcasting an Flowable to \" +\n              \"an implementation type like FlowableProcessor in a closed codebase. Doing this incurs \" +\n              \"needless runtime memory and performance overhead. FlowableProcessor extends from \" +\n              \"Flowable and can be supplied to functions accepting Flowable directly. When \" +\n              \"returning a FlowableProcessor, declare the return type explicitly as Flowable \" +\n              \"(e.g., fun foo(): Flowable<Foo> = fooProcessor).\",\n        ),\n        DenyListedEntry(\n          className = \"io.reactivex.rxjava3.core.Completable\",\n          functionName = \"hide\",\n          errorMessage =\n            \"There should be no reason to defend against downcasting a Completable to \" +\n              \"an implementation type like CompletableSubject in a closed codebase. Doing this incurs \" +\n              \"needless runtime memory and performance overhead. CompletableSubject extends from \" +\n              \"Completable and can be supplied to functions accepting Completable directly. When \" +\n              \"returning a CompletableSubject, declare the return type explicitly as Completable \" +\n              \"(e.g., fun foo(): Completable<Foo> = fooSubject).\",\n        ),\n        DenyListedEntry(\n          className = \"io.reactivex.rxjava3.core.Maybe\",\n          functionName = \"hide\",\n          errorMessage =\n            \"There should be no reason to defend against downcasting a Maybe to \" +\n              \"an implementation type like MaybeSubject in a closed codebase. Doing this incurs \" +\n              \"needless runtime memory and performance overhead. MaybeSubject extends from \" +\n              \"Maybe and can be supplied to functions accepting Maybe directly. When \" +\n              \"returning a MaybeSubject, declare the return type explicitly as Maybe \" +\n              \"(e.g., fun foo(): Maybe<Foo> = fooSubject).\",\n        ),\n        DenyListedEntry(\n          className = \"io.reactivex.rxjava3.core.Single\",\n          functionName = \"hide\",\n          errorMessage =\n            \"There should be no reason to defend against downcasting a Single to \" +\n              \"an implementation type like SingleSubject in a closed codebase. Doing this incurs \" +\n              \"needless runtime memory and performance overhead. SingleSubject extends from \" +\n              \"Single and can be supplied to functions accepting Single directly. When \" +\n              \"returning a SingleSubject, declare the return type explicitly as Single \" +\n              \"(e.g., fun foo(): Single<Foo> = fooSubject).\",\n        ),\n        DenyListedEntry(\n          className = \"androidx.core.content.ContextCompat\",\n          functionName = \"getDrawable\",\n          parameters = listOf(\"android.content.Context\", \"int\"),\n          errorMessage = \"Use Context#getDrawableCompat() instead\",\n        ),\n        DenyListedEntry(\n          className = \"androidx.core.content.res.ResourcesCompat\",\n          functionName = \"getDrawable\",\n          parameters = listOf(\"android.content.Context\", \"int\"),\n          errorMessage = \"Use Context#getDrawableCompat() instead\",\n        ),\n        DenyListedEntry(\n          className = \"android.support.test.espresso.matcher.ViewMatchers\",\n          functionName = \"withId\",\n          parameters = listOf(\"int\"),\n          errorMessage =\n            \"Consider matching the content description instead. IDs are \" +\n              \"implementation details of how a screen is built, not how it works. You can't\" +\n              \" tell a user to click on the button with ID 428194727 so our tests should not\" +\n              \" be doing that. \",\n        ),\n        DenyListedEntry(\n          className = \"android.view.View\",\n          functionName = \"setOnClickListener\",\n          parameters = listOf(\"android.view.View.OnClickListener\"),\n          arguments = listOf(\"null\"),\n          errorMessage =\n            \"This fails to also set View#isClickable. Use View#clearOnClickListener() instead\",\n        ),\n        DenyListedEntry(\n          // If you are deny listing an extension method you need to ascertain the fully qualified\n          // name\n          // of the class the extension method ends up on.\n          className = \"kotlinx.coroutines.flow.FlowKt__CollectKt\",\n          functionName = \"launchIn\",\n          errorMessage =\n            \"Use the structured concurrent CoroutineScope#launch and Flow#collect \" +\n              \"APIs instead of reactive Flow#onEach and Flow#launchIn. Suspend calls like Flow#collect \" +\n              \"can be refactored into standalone suspend funs and mixed in with regular control flow \" +\n              \"in a suspend context, but calls that invoke CoroutineScope#launch and Flow#collect at \" +\n              \"the same time hide the suspend context, encouraging the developer to continue working in \" +\n              \"the reactive domain.\",\n        ),\n        DenyListedEntry(\n          className = \"androidx.viewpager2.widget.ViewPager2\",\n          functionName = \"setId\",\n          parameters = listOf(\"int\"),\n          arguments = listOf(\"ViewCompat.generateViewId()\"),\n          errorMessage =\n            \"Use an id defined in resources or a statically created instead of generating with ViewCompat.generateViewId(). See https://issuetracker.google.com/issues/185820237\",\n        ),\n        DenyListedEntry(\n          className = \"androidx.viewpager2.widget.ViewPager2\",\n          functionName = \"setId\",\n          parameters = listOf(\"int\"),\n          arguments = listOf(\"View.generateViewId()\"),\n          errorMessage =\n            \"Use an id defined in resources or a statically created instead of generating with View.generateViewId(). See https://issuetracker.google.com/issues/185820237\",\n        ),\n        DenyListedEntry(\n          className = \"java.util.LinkedList\",\n          functionName = \"<init>\",\n          errorMessage =\n            \"For a stack/queue/double-ended queue use ArrayDeque, for a list use ArrayList. Both are more efficient internally.\",\n        ),\n        DenyListedEntry(\n          className = \"java.util.Stack\",\n          functionName = \"<init>\",\n          errorMessage = \"For a stack use ArrayDeque which is more efficient internally.\",\n        ),\n        DenyListedEntry(\n          className = \"java.util.Vector\",\n          functionName = \"<init>\",\n          errorMessage =\n            \"For a vector use ArrayList or ArrayDeque which are more efficient internally.\",\n        ),\n        DenyListedEntry(\n          className = \"io.reactivex.rxjava3.schedulers.Schedulers\",\n          functionName = \"newThread\",\n          errorMessage =\n            \"Use a scheduler which wraps a cached set of threads. There should be no reason to be arbitrarily creating threads on Android.\",\n        ),\n        // TODO this would conflict with MagicNumber in detekt, revisit\n        //      DenyListedEntry(\n        //        className = \"android.os.Build.VERSION_CODES\",\n        //        fieldName = MatchAll,\n        //        errorMessage =\n        //        \"No one remembers what these constants map to. Use the API level integer value\n        //  directly since it's self-defining.\"\n        //      ),\n        // TODO we should do this too, but don't currently.\n        //    DenyListedEntry(\n        //      className = \"java.time.Instant\",\n        //      functionName = \"now\",\n        //      errorMessage = \"Use com.squareup.cash.util.Clock to get the time.\"\n        //    ),\n        DenyListedEntry(\n          className = \"kotlinx.coroutines.rx3.RxCompletableKt\",\n          functionName = \"rxCompletable\",\n          errorMessage =\n            \"rxCompletable defaults to Dispatchers.Default. Provide an explicit dispatcher which can be replaced with a test dispatcher to make your tests more deterministic.\",\n          parameters =\n            listOf(\n              \"kotlin.coroutines.CoroutineContext\",\n              \"kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object>\",\n            ),\n          arguments = listOf(\"*\"),\n        ),\n        DenyListedEntry(\n          className = \"kotlinx.coroutines.rx3.RxMaybeKt\",\n          functionName = \"rxMaybe\",\n          errorMessage =\n            \"rxMaybe defaults to Dispatchers.Default. Provide an explicit dispatcher which can be replaced with a test dispatcher to make your tests more deterministic.\",\n          parameters =\n            listOf(\n              \"kotlin.coroutines.CoroutineContext\",\n              \"kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,? extends java.lang.Object>\",\n            ),\n          arguments = listOf(\"*\"),\n        ),\n        DenyListedEntry(\n          className = \"kotlinx.coroutines.rx3.RxSingleKt\",\n          functionName = \"rxSingle\",\n          errorMessage =\n            \"rxSingle defaults to Dispatchers.Default. Provide an explicit dispatcher which can be replaced with a test dispatcher to make your tests more deterministic.\",\n          parameters =\n            listOf(\n              \"kotlin.coroutines.CoroutineContext\",\n              \"kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,? extends java.lang.Object>\",\n            ),\n          arguments = listOf(\"*\"),\n        ),\n        DenyListedEntry(\n          className = \"kotlinx.coroutines.rx3.RxObservableKt\",\n          functionName = \"rxObservable\",\n          errorMessage =\n            \"rxObservable defaults to Dispatchers.Default. Provide an explicit dispatcher which can be replaced with a test dispatcher to make your tests more deterministic.\",\n          parameters =\n            listOf(\n              \"kotlin.coroutines.CoroutineContext\",\n              \"kotlin.jvm.functions.Function2<? super kotlinx.coroutines.channels.ProducerScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object>\",\n            ),\n          arguments = listOf(\"*\"),\n        ),\n        DenyListedEntry(\n          className = \"java.util.Date\",\n          functionName = MatchAll,\n          errorMessage =\n            \"Use java.time.Instant or java.time.ZonedDateTime instead. There is no reason to use java.util.Date in Java 8+.\",\n        ),\n        DenyListedEntry(\n          className = \"java.util.Calendar\",\n          fieldName = MatchAll,\n          errorMessage =\n            \"Use java.time.Instant or java.time.ZonedDateTime instead. There is no reason to use java.util.Calendar in Java 8+.\",\n        ),\n        DenyListedEntry(\n          className = \"java.util.Calendar\",\n          functionName = MatchAll,\n          errorMessage =\n            \"Use java.time.Instant or java.time.ZonedDateTime instead. There is no reason to use java.util.Calendar in Java 8+.\",\n        ),\n        DenyListedEntry(\n          className = \"java.text.DateFormat\",\n          fieldName = MatchAll,\n          errorMessage =\n            \"Use java.time.DateTimeFormatter instead. There is no reason to use java.text.DateFormat in Java 8+.\",\n        ),\n        DenyListedEntry(\n          className = \"java.text.SimpleDateFormat\",\n          fieldName = MatchAll,\n          errorMessage =\n            \"Use java.time.DateTimeFormatter instead. There is no reason to use java.text.DateFormat in Java 8+.\",\n        ),\n        DenyListedEntry(\n          className = \"java.text.DateFormat\",\n          functionName = MatchAll,\n          errorMessage =\n            \"Use java.time.DateTimeFormatter instead. There is no reason to use java.text.DateFormat in Java 8+.\",\n        ),\n        DenyListedEntry(\n          className = \"java.text.SimpleDateFormat\",\n          functionName = MatchAll,\n          errorMessage =\n            \"Use java.time.DateTimeFormatter instead. There is no reason to use java.text.DateFormat in Java 8+.\",\n        ),\n        DenyListedEntry(\n          className = \"kotlin.ResultKt\",\n          functionName = \"runCatching\",\n          errorMessage =\n            \"runCatching has hidden issues when used with coroutines as it catches and doesn't rethrow CancellationException. \" +\n              \"This can interfere with coroutines cancellation handling! \" +\n              \"Prefer catching specific exceptions based on the current case.\",\n        ),\n        // Blocking calls\n        DenyListedEntry(\n          className = \"kotlinx.coroutines.BuildersKt\",\n          functionName = \"runBlocking\",\n          errorMessage =\n            \"Blocking calls in coroutines can cause deadlocks and application jank. \" +\n              \"Prefer making the enclosing function a suspend function or refactoring this in a way to use non-blocking calls. \" +\n              \"If running in a test, use runTest {} or Turbine to test synchronous values.\",\n          issue = BLOCKING_ISSUE,\n        ),\n        *rxJavaBlockingCalls().toTypedArray(),\n      )\n\n    val ISSUES = CONFIG.issues\n\n    private fun createIssue(\n      id: String,\n      briefDescription: String = \"Deny-listed API\",\n      explanation: String =\n        \"This lint check flags usages of APIs in external libraries that we prefer not to use.\",\n    ): Issue {\n      return Issue.create(\n        id = id,\n        briefDescription = briefDescription,\n        explanation = explanation,\n        category = CORRECTNESS,\n        priority = 5,\n        severity = ERROR,\n        implementation =\n          Implementation(\n            DenyListedApiDetector::class.java,\n            EnumSet.of(Scope.JAVA_FILE, Scope.RESOURCE_FILE, Scope.TEST_SOURCES),\n            EnumSet.of(Scope.JAVA_FILE),\n            EnumSet.of(Scope.RESOURCE_FILE),\n            EnumSet.of(Scope.TEST_SOURCES),\n          ),\n      )\n    }\n  }\n}\n\ndata class DenyListedEntry(\n  val className: String,\n  /** The function name to match, [MatchAll] to match all functions, or null if matching a field. */\n  val functionName: String? = null,\n  /** The field name to match, [MatchAll] to match all fields, or null if matching a function. */\n  val fieldName: String? = null,\n  /** Fully-qualified types of function parameters to match, or null to match all overloads. */\n  val parameters: List<String>? = null,\n  /** Argument expressions to match at the call site, or null to match all invocations. */\n  val arguments: List<String>? = null,\n  val errorMessage: String,\n  /**\n   * Option to allow this issue in tests. Should _only_ be reserved for invocations that make sense\n   * in tests.\n   */\n  val allowInTests: Boolean = false,\n  /**\n   * Issue that should be reported for this entry. Defaults to [DenyListedApiDetector.DEFAULT_ISSUE]\n   */\n  val issue: Issue = DenyListedApiDetector.DEFAULT_ISSUE,\n) {\n  init {\n    require((functionName == null) xor (fieldName == null)) {\n      \"One of functionName or fieldName must be set\"\n    }\n  }\n\n  companion object {\n    const val MatchAll = \"*\"\n  }\n}\n\nprivate fun rxJavaBlockingCalls() =\n  listOf(\n      \"io.reactivex.rxjava3.core.Completable\" to listOf(\"blockingAwait\"),\n      \"io.reactivex.rxjava3.core.Single\" to listOf(\"blockingGet\", \"blockingSubscribe\"),\n      \"io.reactivex.rxjava3.core.Maybe\" to listOf(\"blockingGet\", \"blockingSubscribe\"),\n      \"io.reactivex.rxjava3.core.Observable\" to\n        listOf(\n          \"blockingFirst\",\n          \"blockingForEach\",\n          \"blockingIterable\",\n          \"blockingLatest\",\n          \"blockingMostRecent\",\n          \"blockingNext\",\n          \"blockingSingle\",\n          \"blockingSubscribe\",\n        ),\n      \"io.reactivex.rxjava3.core.Flowable\" to\n        listOf(\n          \"blockingFirst\",\n          \"blockingForEach\",\n          \"blockingIterable\",\n          \"blockingLatest\",\n          \"blockingMostRecent\",\n          \"blockingNext\",\n          \"blockingSingle\",\n          \"blockingSubscribe\",\n        ),\n    )\n    .flatMap { (className, methods) ->\n      val shortType = className.substringAfterLast('.')\n      val isCompletable = shortType == \"Completable\"\n      val orMessage =\n        if (!isCompletable) {\n          \" Completable (if you want to hide emission values but defer subscription),\"\n        } else {\n          \"\"\n        }\n      methods.map { method ->\n        DenyListedEntry(\n          className = className,\n          functionName = method,\n          errorMessage =\n            \"Blocking calls in RxJava can cause deadlocks and application jank. \" +\n              \"Prefer making the enclosing method/function return this $shortType, a Disposable to grant control to the caller,$orMessage or refactoring this in a way to use non-blocking calls. \" +\n              \"If running in a test, use the .test()/TestObserver API (https://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/observers/TestObserver.html) test synchronous values.\",\n          issue = DenyListedApiDetector.BLOCKING_ISSUE,\n        )\n      }\n    }\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/eithernet/DoNotExposeEitherNetInRepositoriesDetector.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.eithernet\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Location\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.isJava\nimport com.intellij.psi.PsiModifierListOwner\nimport com.intellij.psi.PsiType\nimport com.intellij.psi.util.PsiTypesUtil\nimport org.jetbrains.kotlin.lexer.KtTokens\nimport org.jetbrains.kotlin.psi.KtModifierListOwner\nimport org.jetbrains.kotlin.psi.KtProperty\nimport org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UField\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.getContainingUClass\nimport org.jetbrains.uast.java.isJava\nimport slack.lint.util.safeReturnType\nimport slack.lint.util.sourceImplementation\n\nprivate const val EITHERNET_PACKAGE = \"com.slack.eithernet\"\n\n/** Reports an error when returning EitherNet types directly in public repository APIs. */\nclass DoNotExposeEitherNetInRepositoriesDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> =\n    listOf(UMethod::class.java, UField::class.java)\n\n  override fun createUastHandler(context: JavaContext) =\n    object : UElementHandler() {\n      val isJava = isJava(context.uastFile?.lang)\n\n      override fun visitMethod(node: UMethod) {\n        if (node.sourcePsi is KtProperty) return // Handled by visitField\n        check(\n          node.isPublic,\n          { node.isRepositoryMember },\n          { node.safeReturnType(context).isEitherNetType },\n          { context.getLocation(node.returnTypeReference ?: node) },\n        )\n      }\n\n      override fun visitField(node: UField) {\n        check(\n          node.isPublic,\n          { node.isRepositoryMember },\n          { node.type.isEitherNetType },\n          { context.getLocation(node.typeReference ?: node) },\n        )\n      }\n\n      private val UElement.isPublic: Boolean\n        get() {\n          if (isJava && this is PsiModifierListOwner) {\n            if (modifierList?.hasExplicitModifier(\"public\") == true) return true\n          }\n          if (sourcePsi is KtModifierListOwner) {\n            val ktModifierListOwner = sourcePsi as KtModifierListOwner\n            val visibility = ktModifierListOwner.visibilityModifierTypeOrDefault()\n            if (visibility == KtTokens.PUBLIC_KEYWORD) return true\n          }\n          return getContainingUClass()?.isInterface == true\n        }\n\n      private fun check(\n        isPublic: Boolean,\n        isRepositoryMember: () -> Boolean,\n        isEitherNetType: () -> Boolean,\n        location: () -> Location,\n      ) {\n        if (!isPublic) return\n        if (!isRepositoryMember()) return\n        if (isEitherNetType()) {\n          context.report(\n            issue = ISSUE,\n            location = location(),\n            message = \"Repository APIs should not expose EitherNet types directly.\",\n          )\n        }\n      }\n\n      private val UElement.isRepositoryMember: Boolean\n        get() {\n          val containingClass = getContainingUClass() ?: return false\n          return containingClass.name?.endsWith(\"Repository\") == true\n        }\n\n      private val PsiType?.isEitherNetType: Boolean\n        get() {\n          return PsiTypesUtil.getPsiClass(this)?.qualifiedName?.startsWith(EITHERNET_PACKAGE) ==\n            true\n        }\n    }\n\n  companion object {\n    private fun Implementation.toIssue(): Issue {\n      return Issue.create(\n        id = \"DoNotExposeEitherNetInRepositories\",\n        briefDescription = \"Repository APIs should not expose EitherNet types directly.\",\n        explanation =\n          \"EitherNet (and networking in general) should be an implementation detail of the repository layer.\",\n        category = Category.CORRECTNESS,\n        priority = 0,\n        severity = Severity.ERROR,\n        implementation = this,\n      )\n    }\n\n    val ISSUE: Issue = sourceImplementation<DoNotExposeEitherNetInRepositoriesDetector>().toIssue()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/inclusive/InclusiveNamingChecker.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\n@file:Suppress(\"UnstableApiUsage\")\n\npackage slack.lint.inclusive\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Context\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Location\nimport com.android.tools.lint.detector.api.Position\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.StringOption\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.android.tools.lint.detector.api.XmlContext\nimport java.util.Locale\nimport org.jetbrains.uast.UElement\nimport org.w3c.dom.Node\nimport slack.lint.util.Priorities\nimport slack.lint.util.resourcesImplementation\nimport slack.lint.util.sourceImplementation\n\nsealed class InclusiveNamingChecker<C : Context, N> {\n  companion object {\n\n    internal val BLOCK_LIST =\n      StringOption(\n        \"block-list\",\n        \"A comma-separated list of words that should not be used in source code.\",\n        null,\n        \"This property should define a comma-separated list of words that should not be used in source code.\",\n      )\n\n    private val SOURCE_ISSUE = sourceImplementation<InclusiveNamingSourceCodeScanner>().toIssue()\n    private val RESOURCES_ISSUE =\n      resourcesImplementation<InclusiveNamingResourceScanner>().toIssue()\n\n    val ISSUES: List<Issue> = listOf(SOURCE_ISSUE, RESOURCES_ISSUE)\n\n    private fun Implementation.toIssue(): Issue {\n      return Issue.create(\n          \"InclusiveNaming\",\n          \"Use inclusive naming.\",\n          \"\"\"\n            We try to use inclusive naming at Slack. Terms such as blacklist, whitelist, master, slave, etc, while maybe \\\n            widely used today, can be socially charged and make others feel excluded or uncomfortable.\n          \"\"\"\n            .trimIndent(),\n          Category.CORRECTNESS,\n          Priorities.NORMAL,\n          Severity.ERROR,\n          this,\n        )\n        .setOptions(listOf(BLOCK_LIST))\n    }\n\n    /** Loads a comma-separated list of blocked words from the [BLOCK_LIST] option. */\n    fun loadBlocklist(context: Context): Set<String> {\n      return BLOCK_LIST.getValue(context.configuration)\n        ?.splitToSequence(\",\")\n        .orEmpty()\n        .map(String::trim)\n        .filter(String::isNotBlank)\n        .toSet()\n    }\n  }\n\n  abstract val context: C\n  abstract val blocklist: Set<String>\n  protected abstract val issue: Issue\n\n  abstract fun locationFor(node: N): Location\n\n  open fun shouldReport(node: N, location: Location, name: String, isFile: Boolean): Boolean = true\n\n  fun check(node: N, name: String?, type: String, isFile: Boolean = false) {\n    if (name == null) return\n    val lowerCased = name.lowercase(Locale.US)\n    blocklist\n      .find { it in lowerCased }\n      ?.let { matched ->\n        val location = locationFor(node)\n        if (!shouldReport(node, location, name, isFile)) return\n        val description = buildString {\n          append(issue.getBriefDescription(TextFormat.TEXT))\n          append(\" Matched string is '\")\n          append(matched)\n          append(\"' in \")\n          append(type)\n          append(\" name '\")\n          append(name)\n          append(\"'\")\n        }\n        context.report(issue, location, description, null)\n      }\n  }\n\n  class SourceCodeChecker(override val context: JavaContext, override val blocklist: Set<String>) :\n    InclusiveNamingChecker<JavaContext, UElement>() {\n    /**\n     * Some element types will be reported multiple times (such as property parameters). This caches\n     * reports so we only report once.\n     */\n    private val cachedReports = mutableSetOf<CacheKey>()\n    override val issue: Issue = SOURCE_ISSUE\n\n    override fun locationFor(node: UElement): Location {\n      return context.getLocation(node)\n    }\n\n    override fun shouldReport(\n      node: UElement,\n      location: Location,\n      name: String,\n      isFile: Boolean,\n    ): Boolean {\n      return cachedReports.add(CacheKey.fromLocation(location, isFile))\n    }\n\n    private data class CacheKey(val location: String) {\n      companion object {\n        fun fromLocation(location: Location, isFile: Boolean): CacheKey {\n          val fileName = location.file.name\n          val start: String\n          val end: String\n          if (isFile) {\n            start = \"\"\n            end = \"\"\n          } else {\n            start = location.start?.lineString ?: \"\"\n            end = location.end?.lineString ?: \"\"\n          }\n          return CacheKey(\"$fileName-$start-$end\")\n        }\n\n        private val Position.lineString: String\n          get() {\n            return \"$line:$column\"\n          }\n      }\n    }\n  }\n\n  class XmlChecker(override val context: XmlContext, override val blocklist: Set<String>) :\n    InclusiveNamingChecker<XmlContext, Node>() {\n    override val issue: Issue = RESOURCES_ISSUE\n\n    override fun locationFor(node: Node): Location {\n      return context.getLocation(node)\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/inclusive/InclusiveNamingResourceScanner.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.inclusive\n\nimport com.android.tools.lint.detector.api.Context\nimport com.android.tools.lint.detector.api.ResourceXmlDetector\nimport com.android.tools.lint.detector.api.XmlContext\nimport org.w3c.dom.Attr\nimport org.w3c.dom.Document\nimport org.w3c.dom.Element\n\n@Suppress(\"UnstableApiUsage\")\nclass InclusiveNamingResourceScanner : ResourceXmlDetector() {\n\n  private lateinit var blocklist: Set<String>\n\n  override fun beforeCheckRootProject(context: Context) {\n    super.beforeCheckRootProject(context)\n    blocklist = InclusiveNamingChecker.loadBlocklist(context)\n  }\n\n  override fun getApplicableElements(): List<String> = ALL\n\n  override fun visitAttribute(context: XmlContext, attribute: Attr) {\n    if (blocklist.isEmpty()) return\n    InclusiveNamingChecker.XmlChecker(context, blocklist)\n      .check(attribute, attribute.name, \"attribute\")\n  }\n\n  override fun visitDocument(context: XmlContext, document: Document) {\n    super.visitDocument(context, document)\n    if (blocklist.isEmpty()) return\n  }\n\n  override fun visitElement(context: XmlContext, element: Element) {\n    if (blocklist.isEmpty()) return\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/inclusive/InclusiveNamingSourceCodeScanner.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.inclusive\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Context\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.isKotlin\nimport org.jetbrains.kotlin.psi.KtProperty\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UField\nimport org.jetbrains.uast.UFile\nimport org.jetbrains.uast.ULabeledExpression\nimport org.jetbrains.uast.ULocalVariable\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.UParameter\nimport org.jetbrains.uast.UVariable\n\n@Suppress(\"UnstableApiUsage\")\nclass InclusiveNamingSourceCodeScanner : Detector(), SourceCodeScanner {\n\n  private lateinit var blocklist: Set<String>\n\n  override fun beforeCheckRootProject(context: Context) {\n    super.beforeCheckRootProject(context)\n    blocklist = InclusiveNamingChecker.loadBlocklist(context)\n  }\n\n  override fun getApplicableUastTypes() =\n    listOf(\n      UFile::class.java,\n      UClass::class.java,\n      UMethod::class.java,\n      UVariable::class.java,\n      ULabeledExpression::class.java,\n    )\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    if (blocklist.isEmpty()) return null\n    val checker = InclusiveNamingChecker.SourceCodeChecker(context, blocklist)\n    context.uastFile?.let { uastFile ->\n      checker.check(uastFile, context.file.name, \"file\", isFile = true)\n    }\n    return object : UElementHandler() {\n      override fun visitFile(node: UFile) {\n        checker.check(node, node.packageName, \"package\")\n      }\n\n      override fun visitClass(node: UClass) {\n        checker.check(node, node.name, \"class\")\n      }\n\n      override fun visitMethod(node: UMethod) {\n        if (node.isConstructor) return\n        val type = if (isKotlin(node.language)) \"function\" else \"method\"\n        checker.check(node, node.name, type)\n      }\n\n      // Covers parameters, properties, fields, and local vars\n      override fun visitVariable(node: UVariable) {\n        val type =\n          when (node) {\n            is UField -> {\n              if (isKotlin(node.language)) {\n                \"property\"\n              } else {\n                \"field\"\n              }\n            }\n            is ULocalVariable -> \"local variable\"\n            is UParameter -> {\n              if (node.sourcePsi is KtProperty) {\n                \"property\"\n              } else {\n                \"parameter\"\n              }\n            }\n            else -> return\n          }\n        checker.check(node, node.name, type)\n      }\n\n      // Covers things like forEach label@ {}\n      override fun visitLabeledExpression(node: ULabeledExpression) {\n        checker.check(node, node.label, \"label\")\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/AnyMockDetector.kt",
    "content": "// Copyright (C) 2024 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.intellij.psi.PsiClass\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.sourceImplementation\n\n/** A [MockDetector.TypeChecker] that checks for any mocking. */\nobject AnyMockDetector : MockDetector.TypeChecker {\n  override val issue: Issue =\n    Issue.create(\n        \"DoNotMockAnything\",\n        \"Do not add new mocks.\",\n        \"\"\"\n        Mocking is almost always unnecessary and will make your tests more brittle. Use real instances \\\n        (if appropriate) or test fakes instead. This lint is a catch-all for mocking, and has been \\\n        enabled in this project to help prevent new mocking from being added.\n      \"\"\",\n        Category.CORRECTNESS,\n        6,\n        // Off by default though\n        Severity.ERROR,\n        sourceImplementation<MockDetector>(),\n      )\n      .setEnabledByDefault(false)\n\n  override fun checkType(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    mockedType: PsiClass,\n  ): MockDetector.Reason = MockDetector.Reason(mockedType, \"Do not add new mocks.\")\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/AutoValueMockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.Severity\nimport slack.lint.util.sourceImplementation\n\n/** A [MockDetector.TypeChecker] that checks for mocking AutoValue classes. */\nobject AutoValueMockDetector : MockDetector.TypeChecker {\n  override val issue: Issue =\n    Issue.create(\n      \"DoNotMockAutoValue\",\n      \"AutoValue classes represent pure data classes, so mocking them should not be necessary.\",\n      \"\"\"\n        `AutoValue` classes represent pure data classes, so mocking them should not be necessary. \\\n        Construct a real instance of the class instead.\n      \"\"\",\n      Category.CORRECTNESS,\n      6,\n      Severity.ERROR,\n      sourceImplementation<MockDetector>(),\n    )\n\n  override val annotations: Set<String> =\n    setOf(\"com.google.auto.value.AutoValue\", \"com.google.auto.value.AutoValue.Builder\")\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/DataClassMockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.intellij.psi.PsiClass\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.sourceImplementation\n\n/** A [MockDetector.TypeChecker] that checks for mocking Kotlin data classes. */\nobject DataClassMockDetector : MockDetector.TypeChecker {\n  override val issue: Issue =\n    Issue.create(\n      \"DoNotMockDataClass\",\n      \"data classes represent pure data classes, so mocking them should not be necessary.\",\n      \"\"\"\n      data classes represent pure data classes, so mocking them should not be necessary. \\\n      Construct a real instance of the class instead.\n    \"\"\",\n      Category.CORRECTNESS,\n      6,\n      Severity.ERROR,\n      sourceImplementation<MockDetector>(),\n    )\n\n  override val annotations: Set<String> = emptySet()\n\n  override fun checkType(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    mockedType: PsiClass,\n  ): MockDetector.Reason? {\n    // Don't warn on records because we have a separate check for that\n    return if (evaluator.isData(mockedType) && !mockedType.hasAnnotation(\"kotlin.jvm.JvmRecord\")) {\n      MockDetector.Reason(\n        mockedType,\n        \"'${mockedType.qualifiedName}' is a data class, so mocking it should not be necessary\",\n      )\n    } else {\n      null\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/DoNotMockMockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.intellij.psi.PsiClass\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.toUElementOfType\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.sourceImplementation\n\n/** A [MockDetector.TypeChecker] that checks for mocking `@DoNotMock`-annotated classes. */\nobject DoNotMockMockDetector : MockDetector.TypeChecker {\n  override val issue: Issue =\n    Issue.create(\n      \"DoNotMock\",\n      \"<Never used>\", // We always compute the brief description.\n      \"\"\"\n      Do not mock classes annotated with `@DoNotMock`, as they are explicitly asking not to be \\\n      mocked in favor of better options (test fakes, etc). These types should define \\\n      explanations/alternatives in their annotation.\n    \"\"\",\n      Category.CORRECTNESS,\n      6,\n      Severity.ERROR,\n      sourceImplementation<MockDetector>(),\n    )\n\n  private const val FQCN_SLACK_DNM = \"slack.lint.annotations.DoNotMock\"\n  private const val FQCN_EP_DNM = \"com.google.errorprone.annotations.DoNotMock\"\n\n  override fun checkType(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    mockedType: PsiClass,\n  ): MockDetector.Reason? {\n    val uMockedType = mockedType.toUElementOfType<UClass>() ?: return null\n    val doNotMockAnnotation =\n      uMockedType.findAnnotation(FQCN_SLACK_DNM)\n        ?: uMockedType.findAnnotation(FQCN_EP_DNM)\n        ?: return null\n\n    val messagePrefix = \"Do not mock ${mockedType.name}\"\n    val suffix = doNotMockAnnotation.findAttributeValue(\"value\")?.evaluate() as String?\n    val message =\n      if (suffix == null) {\n        messagePrefix\n      } else {\n        \"$messagePrefix: $suffix\"\n      }\n    return MockDetector.Reason(mockedType, message)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/ErrorProneDoNotMockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat.TEXT\nimport org.jetbrains.uast.UClass\nimport slack.lint.util.sourceImplementation\n\n/**\n * A simple checker that checks for use of error-prone's `@DoNotMock` and suggests replacing with\n * our slack.lint.annotation version.\n */\nclass ErrorProneDoNotMockDetector : Detector(), SourceCodeScanner {\n  companion object {\n    val ISSUE: Issue =\n      Issue.create(\n        \"ErrorProneDoNotMockUsage\",\n        \"Use Slack's internal `@DoNotMock` annotation.\",\n        \"\"\"\n      While error-prone has a `@DoNotMock` annotation, prefer to use Slack's internal one as it's \\\n      not specific to error-prone and won't go away in a Java-less world.\n      \"\"\",\n        Category.CORRECTNESS,\n        6,\n        Severity.ERROR,\n        sourceImplementation<ErrorProneDoNotMockDetector>(),\n      )\n\n    private const val FQCN_SLACK_DNM = \"slack.lint.annotations.DoNotMock\"\n    private const val FQCN_EP_DNM = \"com.google.errorprone.annotations.DoNotMock\"\n  }\n\n  override fun getApplicableUastTypes() = listOf(UClass::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitClass(node: UClass) {\n        node.findAnnotation(FQCN_EP_DNM)?.let { epDoNotMock ->\n          val replacement =\n            if (\"@$FQCN_EP_DNM\" in epDoNotMock.sourcePsi!!.text) {\n              \"@$FQCN_EP_DNM\"\n            } else {\n              \"@DoNotMock\"\n            }\n          context.report(\n            ISSUE,\n            context.getLocation(epDoNotMock),\n            ISSUE.getBriefDescription(TEXT),\n            quickfixData =\n              fix()\n                .replace()\n                .name(\"Replace with slack.lint.annotations.DoNotMock\")\n                .range(context.getLocation(epDoNotMock))\n                .shortenNames()\n                .text(replacement)\n                .with(\"@$FQCN_SLACK_DNM\")\n                .autoFix()\n                .build(),\n          )\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/MockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Context\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.StringOption\nimport com.android.tools.lint.detector.api.isJava\nimport com.android.tools.lint.detector.api.isKotlin\nimport com.intellij.psi.PsiClass\nimport com.intellij.psi.PsiClassType\nimport java.util.Locale\nimport kotlin.io.path.bufferedWriter\nimport kotlin.io.path.createDirectories\nimport kotlin.io.path.createFile\nimport kotlin.io.path.deleteExisting\nimport kotlin.io.path.exists\nimport org.jetbrains.kotlin.psi.KtProperty\nimport org.jetbrains.uast.UAnnotated\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UClassLiteralExpression\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UField\nimport org.jetbrains.uast.UReferenceExpression\nimport org.jetbrains.uast.UastCallKind\nimport slack.lint.mocking.MockDetector.Companion.TYPE_CHECKERS\nimport slack.lint.mocking.MockDetector.TypeChecker\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.OptionLoadingDetector\nimport slack.lint.util.StringSetLintOption\n\nprivate data class MockFactory(val declarationContainer: String, val factoryName: String)\n\n/**\n * A detector for detecting different kinds of mocking behavior. Implementations of [TypeChecker]\n * can indicate annotated types that should be reported via [TypeChecker.checkType] or\n * [TypeChecker.annotations] for more dynamic control.\n *\n * New [TypeChecker] implementations should be added to [TYPE_CHECKERS] to run in this.\n */\nclass MockDetector\n@JvmOverloads\nconstructor(\n  private val mockAnnotationsOption: StringSetLintOption = StringSetLintOption(MOCK_ANNOTATIONS),\n  private val mockFactoriesOption: StringSetLintOption = StringSetLintOption(MOCK_FACTORIES),\n  private val mockReport: StringOption = MOCK_REPORT,\n) : OptionLoadingDetector(mockAnnotationsOption, mockFactoriesOption), SourceCodeScanner {\n  companion object {\n\n    internal const val MOCK_REPORT_PATH = \"build/reports/mockdetector/mock-report.csv\"\n\n    internal val MOCK_ANNOTATIONS =\n      StringOption(\n        \"mock-annotations\",\n        \"A comma-separated list of mock annotations.\",\n        \"org.mockito.Mock,org.mockito.Spy\",\n        \"This property should define comma-separated list of mock annotation class names (FQCN). Set this for all issues using this option.\",\n      )\n\n    internal val MOCK_FACTORIES =\n      StringOption(\n        \"mock-factories\",\n        \"A comma-separated list of mock factories (org.mockito.Mockito#methodName).\",\n        \"org.mockito.Mockito#mock,org.mockito.Mockito#spy,slack.test.mockito.MockitoHelpers#mock,slack.test.mockito.MockitoHelpersKt#mock,org.mockito.kotlin.MockingKt#mock,org.mockito.kotlin.SpyingKt#spy\",\n        \"A comma-separated list of mock factories (org.mockito.Mockito#methodName). Set this for all issues using this option.\",\n      )\n\n    internal val MOCK_REPORT =\n      StringOption(\n        \"mock-report\",\n        \"If enabled, writes a mock report to <project-dir>/$MOCK_REPORT_PATH.\",\n        \"none\",\n        \"If enabled, writes a mock report to <project-dir>/$MOCK_REPORT_PATH. The format of the file is a csv of (type,isError) of mocked classes. Set this for all issues using this option.\",\n      )\n\n    private val TYPE_CHECKERS =\n      listOf(\n        // Loosely defined in the order of most likely to be hit\n        AnyMockDetector,\n        PlatformTypeMockDetector,\n        DataClassMockDetector,\n        DoNotMockMockDetector,\n        SealedClassMockDetector,\n        AutoValueMockDetector,\n        ObjectClassMockDetector,\n        RecordClassMockDetector,\n      )\n    private val OPTIONS = listOf(MOCK_ANNOTATIONS, MOCK_FACTORIES, MOCK_REPORT)\n    val ALL_ISSUES = TYPE_CHECKERS.map { it.issue.setOptions(OPTIONS) }\n    val ISSUES = ALL_ISSUES.filterNot { it == AnyMockDetector.issue }.toTypedArray()\n  }\n\n  // A mapping of mocked types\n  private val reports = mutableListOf<Pair<String, Boolean>>()\n\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java, UField::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    if (!context.isTestSource) return null\n\n    val checkers =\n      TYPE_CHECKERS.filter { context.isEnabled(it.issue) }\n        .ifEmpty {\n          return null\n        }\n    val slackEvaluator = MetadataJavaEvaluator(context.file.name, context.evaluator)\n\n    val reportingMode = reportMode(context)\n\n    val reportErrors = reportingMode == MockReportMode.ALL || reportingMode == MockReportMode.ERRORS\n    val reportAll = reportingMode == MockReportMode.ALL\n\n    val mockFactories: Map<String, Set<String>> =\n      mockFactoriesOption.value\n        .map { factory ->\n          val (declarationContainer, factoryName) = factory.split(\"#\")\n          MockFactory(declarationContainer, factoryName)\n        }\n        .groupBy { it.factoryName }\n        .mapValues { it.value.mapTo(mutableSetOf()) { it.declarationContainer } }\n\n    return object : UElementHandler() {\n\n      // Checks for mock()/spy() calls\n      override fun visitCallExpression(node: UCallExpression) {\n        // We only want method calls\n        if (node.kind != UastCallKind.METHOD_CALL) return\n\n        // Check our known mock methods\n        val mockFactoryContainers =\n          mockFactories[node.methodName]?.takeIf { it.isNotEmpty() } ?: return\n\n        // Matches a known method, now check if this one's in that method's known declaration\n        // container\n        val resolvedContainer =\n          node.resolve()?.let {\n            it.containingClass?.qualifiedName ?: context.evaluator.getPackage(it)?.qualifiedName\n          } ?: return\n\n        if (resolvedContainer !in mockFactoryContainers) return\n\n        // Now resolve the mocked type\n        var argumentType: PsiClass? = null\n        val expressionType = node.getExpressionType()\n        when {\n          expressionType != null -> {\n            argumentType = slackEvaluator.getTypeClass(expressionType)\n          }\n          node.typeArgumentCount == 1 -> {\n            // We can read the type here for the fun <reified T> mock() helpers\n            argumentType = slackEvaluator.getTypeClass(node.typeArguments[0])\n          }\n          node.valueArgumentCount != 0 -> {\n            when (val firstArg = node.valueArguments[0]) {\n              is UClassLiteralExpression -> {\n                // It's Foo.class, we can just use it directly\n                argumentType = slackEvaluator.getTypeClass(firstArg.type)\n              }\n              is UReferenceExpression -> {\n                val type = firstArg.getExpressionType()\n                if (node.methodName == \"spy\") {\n                  // spy takes an instance, so take the type at face value\n                  argumentType = slackEvaluator.getTypeClass(type)\n                } else if (type is PsiClassType && type.parameterCount == 1) {\n                  // If it's a Class and not a \"spy\" method, assume it's the mock type\n                  val classGeneric = type.parameters[0]\n                  argumentType = slackEvaluator.getTypeClass(classGeneric)\n                }\n              }\n            }\n          }\n        }\n\n        argumentType?.let { checkMock(node, argumentType) }\n      }\n\n      // Checks properties and fields, usually annotated with @Mock/@Spy\n      override fun visitField(node: UField) {\n        if (isKotlin(node.language)) {\n          val sourcePsi = node.sourcePsi ?: return\n          if (sourcePsi is KtProperty && isMockAnnotated(node)) {\n            val type = slackEvaluator.getTypeClass(node.type) ?: return\n            checkMock(node, type)\n            return\n          }\n        } else if (isJava(node.language) && isMockAnnotated(node)) {\n          val type = slackEvaluator.getTypeClass(node.type) ?: return\n          checkMock(node, type)\n          return\n        }\n      }\n\n      private fun isMockAnnotated(node: UAnnotated): Boolean {\n        return mockAnnotationsOption.value.any { node.findAnnotation(it) != null }\n      }\n\n      private fun addReport(type: PsiClass, isError: Boolean) {\n        if (reportAll || (isError && reportErrors)) {\n          type.qualifiedName?.let { reports += (it to isError) }\n        }\n      }\n\n      private fun checkMock(node: UElement, type: PsiClass) {\n        for (checker in checkers) {\n          val reason = checker.checkType(context, slackEvaluator, type)\n          if (reason != null) {\n            addReport(type, isError = true)\n            context.report(checker.issue, context.getLocation(node), reason.reason)\n            return\n          }\n          val disallowedAnnotation = checker.annotations.find { type.hasAnnotation(it) } ?: continue\n          addReport(type, isError = true)\n          context.report(\n            checker.issue,\n            context.getLocation(node),\n            \"Mocked type is annotated with non-mockable annotation $disallowedAnnotation\",\n          )\n          return\n        }\n        addReport(type, isError = false)\n      }\n    }\n  }\n\n  private fun reportMode(context: Context): MockReportMode {\n    return mockReport.getValue(context)?.let { MockReportMode.valueOf(it.uppercase(Locale.US)) }\n      ?: MockReportMode.NONE\n  }\n\n  override fun afterCheckEachProject(context: Context) {\n    if (reportMode(context) != MockReportMode.NONE && reports.isNotEmpty()) {\n      val outputFile = context.project.dir.toPath().resolve(MOCK_REPORT_PATH)\n      if (outputFile.exists()) {\n        outputFile.deleteExisting()\n      }\n      // TODO use createParentDirectories() when we upgrade to Kotlin 1.9\n      outputFile.parent.createDirectories()\n      outputFile.createFile()\n      outputFile.bufferedWriter().use { writer ->\n        writer.write(\n          reports\n            .sortedBy { it.first }\n            .joinToString(prefix = \"type,isError\\n\", separator = \"\\n\") { (type, isError) ->\n              \"$type,$isError\"\n            }\n        )\n      }\n    }\n    reports.clear()\n  }\n\n  interface TypeChecker {\n    val issue: Issue\n\n    /** Set of annotation FQCNs that should not be mocked */\n    val annotations: Set<String>\n      get() = emptySet()\n\n    fun checkType(\n      context: JavaContext,\n      evaluator: MetadataJavaEvaluator,\n      mockedType: PsiClass,\n    ): Reason? {\n      return null\n    }\n  }\n\n  /**\n   * @property type a [PsiClass] object representing the class that should not be mocked.\n   * @property reason The reason this class should not be mocked, which may be as simple as \"it is\n   *   annotated to forbid mocking\" but may also provide a suggested workaround.\n   */\n  data class Reason(val type: PsiClass, val reason: String)\n\n  /** Represents different modes for generating mock reports. */\n  enum class MockReportMode {\n    /** The default – no reporting is done. */\n    NONE,\n    /** Only DoNotMock errors are reported. */\n    ERRORS,\n    /** All mocked types are reported, even types that are not errors. */\n    ALL,\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/ObjectClassMockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.intellij.psi.PsiClass\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.sourceImplementation\n\n/** A [MockDetector.TypeChecker] that checks for mocking Kotlin object classes. */\nobject ObjectClassMockDetector : MockDetector.TypeChecker {\n  override val issue: Issue =\n    Issue.create(\n      \"DoNotMockObjectClass\",\n      \"object classes are singletons, so mocking them should not be necessary\",\n      \"\"\"\n      object classes are global singletons, so mocking them should not be necessary. \\\n      Use the object instance instead.\n    \"\"\",\n      Category.CORRECTNESS,\n      6,\n      Severity.ERROR,\n      sourceImplementation<MockDetector>(),\n    )\n\n  override val annotations: Set<String> = emptySet()\n\n  override fun checkType(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    mockedType: PsiClass,\n  ): MockDetector.Reason? {\n    return if (evaluator.isObject(mockedType)) {\n      MockDetector.Reason(\n        mockedType,\n        \"'${mockedType.qualifiedName}' is an object, so mocking it should not be necessary\",\n      )\n    } else {\n      null\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/PlatformTypeMockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.intellij.psi.PsiClass\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.sourceImplementation\n\n/** A [MockDetector.TypeChecker] that checks for mocking platform types. */\nobject PlatformTypeMockDetector : MockDetector.TypeChecker {\n  private val platforms =\n    setOf(\"android\", \"androidx\", \"java\", \"javax\", \"kotlin\", \"kotlinx\", \"scala\")\n\n  override val issue: Issue =\n    Issue.create(\n      \"DoNotMockPlatformTypes\",\n      \"platform types should not be mocked\",\n      \"\"\"\n      Platform types (i.e. classes in java.*, android.*, etc) should not be mocked. \\\n      Use a real instance or fake instead. Mocking platform types like these can lead \\\n      to surprising test failures due to mocks that actually behave differently than \\\n      real instances, especially when passed into real implementations that use them \\\n      and expect them to behave in a certain way. If using Robolectric, consider using \\\n      its shadow APIs.\n    \"\"\",\n      Category.CORRECTNESS,\n      6,\n      Severity.ERROR,\n      sourceImplementation<MockDetector>(),\n    )\n\n  override fun checkType(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    mockedType: PsiClass,\n  ): MockDetector.Reason? {\n    val name = mockedType.qualifiedName ?: return null\n    val isPlatformType = name.substringBefore('.') in platforms\n    return if (isPlatformType) {\n      MockDetector.Reason(mockedType, \"platform type '$name' should not be mocked\")\n    } else {\n      null\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/RecordClassMockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.isJava\nimport com.intellij.psi.PsiClass\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.sourceImplementation\n\n/** A [MockDetector.TypeChecker] that checks for mocking record classes. */\nobject RecordClassMockDetector : MockDetector.TypeChecker {\n  override val issue: Issue =\n    Issue.create(\n      \"DoNotMockRecordClass\",\n      \"record classes represent pure data classes, so mocking them should not be necessary.\",\n      \"\"\"\n      record classes represent pure data classes, so mocking them should not be necessary. \\\n      Construct a real instance of the class instead.\n    \"\"\",\n      Category.CORRECTNESS,\n      6,\n      Severity.ERROR,\n      sourceImplementation<MockDetector>(),\n    )\n\n  override val annotations: Set<String> = emptySet()\n\n  override fun checkType(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    mockedType: PsiClass,\n  ): MockDetector.Reason? {\n    val isRecord =\n      if (isJava(mockedType.language)) {\n        // Java\n        mockedType.isRecord\n      } else {\n        // Kotlin. Check the annotation first as the isData check may check metadata\n        mockedType.hasAnnotation(\"kotlin.jvm.JvmRecord\") && evaluator.isData(mockedType)\n      }\n    return if (isRecord) {\n      MockDetector.Reason(\n        mockedType,\n        \"'${mockedType.qualifiedName}' is a record class, so mocking it should not be necessary\",\n      )\n    } else {\n      null\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/SealedClassMockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.intellij.psi.PsiClass\nimport com.intellij.psi.impl.light.LightElement\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.sourceImplementation\n\n/** A [MockDetector.TypeChecker] that checks for mocking Kotlin sealed classes. */\nobject SealedClassMockDetector : MockDetector.TypeChecker {\n  override val issue: Issue =\n    Issue.create(\n      \"DoNotMockSealedClass\",\n      \"sealed classes have a restricted type hierarchy, use a subtype instead\",\n      \"\"\"\n      sealed classes have a restricted type hierarchy, so creating new unrestricted mocks \\\n      will break runtime expectations. Mockito also cannot mock sealed classes in Java 17+. \\\n      Use a subtype instead.\n    \"\"\",\n      Category.CORRECTNESS,\n      6,\n      Severity.ERROR,\n      sourceImplementation<MockDetector>(),\n    )\n\n  override val annotations: Set<String> = emptySet()\n\n  override fun checkType(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    mockedType: PsiClass,\n  ): MockDetector.Reason? {\n    // Check permitsList to cover Java 17 sealed types too\n    return if (evaluator.isSealed(mockedType) || mockedType.hasPermitsList()) {\n      MockDetector.Reason(\n        mockedType,\n        \"'${mockedType.qualifiedName}' is a sealed type and has a restricted type hierarchy, use a subtype instead.\",\n      )\n    } else {\n      null\n    }\n  }\n\n  private fun PsiClass.hasPermitsList(): Boolean {\n    return if (this is LightElement) {\n      // TODO https://issuetracker.google.com/issues/428697839\n      false\n    } else {\n      permitsList != null\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/mocking/ValueClassMockDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.intellij.psi.PsiClass\nimport slack.lint.util.MetadataJavaEvaluator\nimport slack.lint.util.isValueClass\nimport slack.lint.util.sourceImplementation\n\n/** A [MockDetector.TypeChecker] that checks for mocking Kotlin value classes. */\n// TODO not currently enabled because of https://issuetracker.google.com/issues/283715187\nobject ValueClassMockDetector : MockDetector.TypeChecker {\n  override val issue: Issue =\n    Issue.create(\n      \"DoNotMockValueClass\",\n      \"value classes represent inlined types, so mocking them should not be necessary.\",\n      \"\"\"\n      value classes represent inlined types, so mocking them should not be necessary. \\\n      Construct a real instance of the class instead.\n    \"\"\",\n      Category.CORRECTNESS,\n      6,\n      Severity.ERROR,\n      sourceImplementation<MockDetector>(),\n    )\n\n  override val annotations: Set<String> = emptySet()\n\n  override fun checkType(\n    context: JavaContext,\n    evaluator: MetadataJavaEvaluator,\n    mockedType: PsiClass,\n  ): MockDetector.Reason? {\n    return if (evaluator.isValueClass(mockedType)) {\n      MockDetector.Reason(\n        mockedType,\n        \"'${mockedType.qualifiedName}' is a value class using inlined types, so mocking it should not be necessary\",\n      )\n    } else {\n      null\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/moshi/MoshiLintUtil.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.moshi\n\nimport com.intellij.psi.PsiClass\n\ninternal object MoshiLintUtil {\n  private const val FQCN_ANNOTATION_ADAPTED_BY = \"dev.zacsweers.moshix.adapters.AdaptedBy\"\n  private const val FQCN_ANNOTATION_JSON_CLASS = \"com.squareup.moshi.JsonClass\"\n\n  fun PsiClass.hasMoshiAnnotation(): Boolean {\n    return hasAnnotation(FQCN_ANNOTATION_ADAPTED_BY) || hasAnnotation(FQCN_ANNOTATION_JSON_CLASS)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/parcel/ParcelizeFunctionPropertyDetector.kt",
    "content": "// Copyright (C) 2023 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.parcel\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.TextFormat\nimport com.android.tools.lint.detector.api.getUMethod\nimport com.intellij.psi.PsiClassType\nimport com.intellij.psi.PsiType\nimport org.jetbrains.kotlin.psi.KtPrimaryConstructor\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.kotlin.isKotlin\nimport slack.lint.parcel.ParcelizeFunctionPropertyDetector.Companion.ISSUE\nimport slack.lint.util.sourceImplementation\n\n/** @see ISSUE */\nclass ParcelizeFunctionPropertyDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> {\n    return listOf(UClass::class.java)\n  }\n\n  override fun createUastHandler(context: JavaContext): UElementHandler? {\n    // Parcelize can only be used in Kotlin files, so this check only checks in Kotlin files\n    if (!isKotlin(context.uastFile?.lang)) return null\n\n    return object : UElementHandler() {\n      override fun visitClass(node: UClass) {\n        if (!node.hasAnnotation(PARCELIZE)) return\n\n        // Primary constructor is required, but Parcelize's inspections will catch this\n        val primaryConstructor =\n          node.constructors\n            .asSequence()\n            .mapNotNull { it.getUMethod() }\n            .firstOrNull { it.sourcePsi is KtPrimaryConstructor } ?: return\n\n        // Now check properties\n        for (parameter in primaryConstructor.uastParameters) {\n          if (parameter.type.isFunctionType && !parameter.hasAnnotation(IGNORED_ON_PARCEL)) {\n            context.report(\n              ISSUE,\n              context.getLocation(parameter.typeReference),\n              ISSUE.getExplanation(TextFormat.TEXT),\n            )\n          }\n        }\n      }\n\n      private val PsiType.isFunctionType: Boolean\n        get() =\n          this is PsiClassType &&\n            resolve()?.qualifiedName?.startsWith(\"kotlin.jvm.functions.Function\") == true\n    }\n  }\n\n  companion object {\n    private const val PARCELIZE_PACKAGE = \"kotlinx.parcelize\"\n    private const val PARCELIZE = \"$PARCELIZE_PACKAGE.Parcelize\"\n    private const val IGNORED_ON_PARCEL = \"$PARCELIZE_PACKAGE.IgnoredOnParcel\"\n\n    internal val ISSUE: Issue =\n      Issue.create(\n        \"ParcelizeFunctionProperty\",\n        \"Function type properties are not parcelable\",\n        \"While technically (and surprisingly) supported by Parcelize, function types \" +\n          \"should not be used in Parcelize classes. There are only limited conditions where it \" +\n          \"will work and it's usually a sign that you're modeling your data wrong.\",\n        Category.CORRECTNESS,\n        9,\n        Severity.ERROR,\n        sourceImplementation<ParcelizeFunctionPropertyDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/resources/FullyQualifiedResourceDetector.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.resources\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Context\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.isKotlin\nimport org.jetbrains.kotlin.psi.KtFile\nimport org.jetbrains.kotlin.psi.KtImportDirective\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UQualifiedReferenceExpression\nimport slack.lint.resources.ImportAliasesLoader.IMPORT_ALIASES\nimport slack.lint.util.sourceImplementation\n\nprivate const val FQN_ANDROID_R = \"android.R\"\nprivate val WHITESPACE_REGEX = \"\\\\s+\".toRegex()\n\n/** Reports an error when an R class is referenced using its fully qualified name. */\nclass FullyQualifiedResourceDetector : Detector(), SourceCodeScanner {\n  private lateinit var importAliases: Map<String, String>\n\n  override fun beforeCheckRootProject(context: Context) {\n    super.beforeCheckRootProject(context)\n\n    importAliases = ImportAliasesLoader.loadImportAliases(context)\n  }\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> =\n    listOf(UQualifiedReferenceExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n\n      override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {\n        // Import alias is a Kotlin feature.\n        if (!isKotlin(node.lang)) return\n\n        val qualifier = node.receiver.asSourceString()\n        val normalized = qualifier.trim().replace(WHITESPACE_REGEX, \"\")\n        if (normalized.endsWith(\".R\") && normalized != FQN_ANDROID_R) {\n          val alias = importAliases[normalized]\n\n          context.report(\n            ISSUE,\n            context.getNameLocation(node.receiver),\n            if (alias != null) \"Use $alias as an import alias instead\"\n            else \"Use an import alias instead\",\n            quickfixData = createLintFix(alias, node, qualifier),\n          )\n        }\n      }\n\n      private fun createLintFix(\n        alias: String?,\n        node: UQualifiedReferenceExpression,\n        qualifier: String,\n      ): LintFix? {\n        return if (alias != null) {\n          val fixes =\n            mutableListOf(\n              fix().replace().range(context.getLocation(node.receiver)).with(alias).build()\n            )\n\n          // Alternative to ReplaceStringBuilder#imports since that one didn't work here.\n          addImportIfMissing(qualifier, alias, fixes)\n\n          fix()\n            .name(\"Replace with import alias\")\n            .composite(*fixes.reversed().toTypedArray())\n            .autoFix()\n        } else {\n          null\n        }\n      }\n\n      private fun addImportIfMissing(\n        qualifier: String,\n        alias: String,\n        issues: MutableList<LintFix>,\n      ) {\n        context.uastFile?.imports?.let { imports ->\n          val importIsMissing =\n            !imports.any {\n              val importDirective = it.sourcePsi as? KtImportDirective\n\n              val importedFqNameString = importDirective?.importedFqName?.asString()?.trim()\n              qualifier == importedFqNameString && alias == importDirective.aliasName\n            }\n\n          if (importIsMissing) {\n            if (imports.isNotEmpty()) {\n              val lastImport = imports.last().sourcePsi as? KtImportDirective\n              addImportAfterLastImport(lastImport, qualifier, alias, issues)\n            } else {\n              addImportAfterPackageName(qualifier, alias, issues)\n            }\n          }\n        }\n      }\n\n      private fun addImportAfterLastImport(\n        lastImport: KtImportDirective?,\n        qualifier: String,\n        alias: String,\n        issues: MutableList<LintFix>,\n      ) {\n        if (lastImport != null) {\n          issues.add(\n            fix()\n              .replace()\n              .range(context.getLocation(lastImport))\n              .with(lastImport.text + System.lineSeparator() + \"import $qualifier as $alias\")\n              .build()\n          )\n        }\n      }\n\n      private fun addImportAfterPackageName(\n        qualifier: String,\n        alias: String,\n        issues: MutableList<LintFix>,\n      ) {\n        (context.psiFile as? KtFile)?.packageDirective?.let { packageDirective ->\n          issues.add(\n            fix()\n              .replace()\n              .range(context.getLocation(packageDirective))\n              .with(\n                packageDirective.text +\n                  System.lineSeparator() +\n                  System.lineSeparator() +\n                  \"import $qualifier as $alias\"\n              )\n              .build()\n          )\n        }\n      }\n    }\n  }\n\n  companion object {\n    val ISSUE: Issue =\n      Issue.create(\n          \"FullyQualifiedResource\",\n          \"Resources should use an import alias instead of being fully qualified.\",\n          \"Resources should use an import alias instead of being fully qualified. For example: \\n\" +\n            \"import slack.l10n.R as L10nR\\n\" +\n            \"...\\n\" +\n            \"...getString(L10nR.string.app_name)\",\n          Category.CORRECTNESS,\n          6,\n          Severity.ERROR,\n          sourceImplementation<FullyQualifiedResourceDetector>(),\n        )\n        .setOptions(listOf(IMPORT_ALIASES))\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/resources/ImportAliasesLoader.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.resources\n\nimport com.android.tools.lint.detector.api.Context\nimport com.android.tools.lint.detector.api.StringOption\n\nobject ImportAliasesLoader {\n\n  internal val IMPORT_ALIASES =\n    StringOption(\n      \"import-aliases\",\n      \"A comma-separated list of package name and their import aliases.\",\n      null,\n      \"This property should define a comma-separated list of package name and their import aliases\" +\n        \" in the format: packageName as importAlias\",\n    )\n\n  /** Loads the import aliases from the [IMPORT_ALIASES] option. */\n  fun loadImportAliases(context: Context): Map<String, String> {\n    return IMPORT_ALIASES.getValue(context.configuration)\n      ?.splitToSequence(\",\")\n      .orEmpty()\n      .map(String::trim)\n      .filter(String::isNotBlank)\n      .map {\n        val (packageName, alias) = it.split(\" as \")\n        packageName.trim() to alias.trim()\n      }\n      .toMap()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/resources/MissingResourceImportAliasDetector.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.resources\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Context\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport org.jetbrains.kotlin.psi.KtImportDirective\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UImportStatement\nimport org.jetbrains.uast.USimpleNameReferenceExpression\nimport org.jetbrains.uast.getContainingUFile\nimport org.jetbrains.uast.getQualifiedParentOrThis\nimport slack.lint.resources.ImportAliasesLoader.IMPORT_ALIASES\nimport slack.lint.resources.model.RootIssueData\nimport slack.lint.util.sourceImplementation\n\n/**\n * Reports an error when an R class, other than the local module's, is imported without an import\n * alias.\n */\nclass MissingResourceImportAliasDetector : Detector(), SourceCodeScanner {\n\n  private val fixes = mutableListOf<LintFix>()\n  private var rootIssueData: RootIssueData? = null\n\n  private lateinit var importAliases: Map<String, String>\n\n  override fun beforeCheckRootProject(context: Context) {\n    super.beforeCheckRootProject(context)\n\n    importAliases = ImportAliasesLoader.loadImportAliases(context)\n  }\n\n  override fun afterCheckFile(context: Context) {\n    // Collect all the fixes and apply them to one issue on the import to avoid adding the import\n    // alias with a fix\n    // and leaving the R references still referencing the non-aliased R or vice versa.\n    rootIssueData?.let {\n      context.report(\n        ISSUE,\n        it.nameLocation,\n        \"Use an import alias for R classes from other modules\",\n        quickfixData =\n          fix()\n            .name(\"Add import alias\")\n            // Apply the fixes in reverse so that the ranges/locations don't change.\n            .composite(*fixes.reversed().toTypedArray())\n            .autoFix(),\n      )\n      rootIssueData = null\n      fixes.clear()\n    }\n  }\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> =\n    listOf(UImportStatement::class.java, USimpleNameReferenceExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n\n      override fun visitImportStatement(node: UImportStatement) {\n        // Import alias is a Kotlin feature.\n        val importDirective = node.sourcePsi as? KtImportDirective ?: return\n\n        if (importDirective.importedName?.identifier == \"R\" && importDirective.aliasName == null) {\n          val packageName = context.project.`package`\n          val filePackageName = node.getContainingUFile()?.packageName\n\n          val importedFqName = importDirective.importedFqName\n          val parentImportedFqName = importedFqName?.parent()?.asString()\n\n          val isLocalResourceImport =\n            parentImportedFqName?.let {\n              if (packageName != null) it == packageName else filePackageName?.startsWith(it)\n            } ?: true\n          if (!isLocalResourceImport) {\n            val importedFqNameString = requireNotNull(importedFqName).asString()\n            val alias = importAliases[importedFqNameString]\n\n            if (alias != null) {\n              rootIssueData =\n                RootIssueData(\n                  alias = alias,\n                  nameLocation = context.getNameLocation(importDirective),\n                )\n\n              fixes.add(createImportLintFix(importDirective, importedFqNameString, alias))\n            } else {\n              context.report(\n                ISSUE,\n                context.getNameLocation(importDirective),\n                \"Use an import alias for R classes from other modules\",\n              )\n            }\n          }\n        }\n      }\n\n      private fun createImportLintFix(\n        importDirective: KtImportDirective,\n        importedFqNameString: String,\n        alias: String?,\n      ): LintFix {\n        return fix()\n          .replace()\n          .range(context.getLocation(importDirective))\n          .text(importedFqNameString)\n          .with(\"$importedFqNameString as $alias\")\n          .build()\n      }\n\n      override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression) {\n        rootIssueData?.alias?.let {\n          if (\n            node.asSourceString() == \"R\" &&\n              // Make sure node is its own parent to safeguard against cases like\n              // Build.VERSION_CODES.R.\n              node.getQualifiedParentOrThis() == node\n          ) {\n            fixes.add(createReferenceLintFix(node, it))\n          }\n        }\n      }\n\n      private fun createReferenceLintFix(\n        node: USimpleNameReferenceExpression,\n        alias: String,\n      ): LintFix {\n        return fix().replace().range(context.getLocation(node)).with(alias).build()\n      }\n    }\n  }\n\n  companion object {\n    val ISSUE: Issue =\n      Issue.create(\n          \"MissingResourceImportAlias\",\n          \"Missing import alias for R class.\",\n          \"\"\"\n          Only the local module's R class is allowed to be imported without an alias. \\\n          Add an import alias for this. For example, import slack.l10n.R as L10nR\n          \"\"\",\n          Category.CORRECTNESS,\n          6,\n          Severity.ERROR,\n          sourceImplementation<MissingResourceImportAliasDetector>(),\n        )\n        .setOptions(listOf(IMPORT_ALIASES))\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/resources/WrongResourceImportAliasDetector.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.resources\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Context\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport org.jetbrains.kotlin.psi.KtImportDirective\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UImportStatement\nimport org.jetbrains.uast.USimpleNameReferenceExpression\nimport slack.lint.resources.ImportAliasesLoader.IMPORT_ALIASES\nimport slack.lint.resources.model.RootIssueData\nimport slack.lint.util.sourceImplementation\n\n/** Reports an error when an R class is imported using the wrong import alias. */\nclass WrongResourceImportAliasDetector : Detector(), SourceCodeScanner {\n\n  private val fixes = mutableListOf<LintFix>()\n  private var rootIssueData: RootIssueData? = null\n\n  private lateinit var importAliases: Map<String, String>\n\n  override fun beforeCheckRootProject(context: Context) {\n    super.beforeCheckRootProject(context)\n\n    importAliases = ImportAliasesLoader.loadImportAliases(context)\n  }\n\n  override fun afterCheckFile(context: Context) {\n    // Collect all the fixes and apply them to one issue on the import to avoid renaming the import\n    // alias with a fix\n    // and leaving the R references still referencing the old import alias or vice versa.\n    rootIssueData?.let {\n      context.report(\n        ISSUE,\n        it.nameLocation,\n        \"Use ${it.alias} as an import alias here\",\n        quickfixData =\n          fix()\n            .name(\"Replace import alias\")\n            // Apply the fixes in reverse so that the ranges/locations don't change.\n            .composite(*fixes.reversed().toTypedArray())\n            .autoFix(),\n      )\n\n      reset()\n    }\n  }\n\n  private fun reset() {\n    rootIssueData = null\n    fixes.clear()\n  }\n\n  override fun getApplicableUastTypes(): List<Class<out UElement>> =\n    listOf(UImportStatement::class.java, USimpleNameReferenceExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      private var wrongAlias: String? = null\n\n      override fun visitImportStatement(node: UImportStatement) {\n        // Import alias is a Kotlin feature.\n        val importDirective = node.sourcePsi as? KtImportDirective ?: return\n\n        // In case of multiple wrong aliases, only fix the first one.\n        if (wrongAlias != null) return\n\n        val importedFqNameString = importDirective.importedFqName?.asString() ?: return\n        if (importedFqNameString.endsWith(\".R\") && importDirective.aliasName != null) {\n          importAliases[importedFqNameString]?.let { alias ->\n            val aliasName = importDirective.aliasName\n            if (alias != aliasName) {\n              this.wrongAlias = aliasName\n              this@WrongResourceImportAliasDetector.rootIssueData =\n                RootIssueData(\n                  alias = alias,\n                  nameLocation = context.getNameLocation(importDirective),\n                )\n\n              fixes.add(createImportLintFix(node, importedFqNameString, aliasName, alias))\n            }\n          }\n        }\n      }\n\n      private fun createImportLintFix(\n        node: UImportStatement,\n        importedFqNameString: String,\n        aliasName: String?,\n        alias: String,\n      ): LintFix {\n        return fix()\n          .replace()\n          .range(context.getLocation(node))\n          .text(\"$importedFqNameString as $aliasName\")\n          .with(\"$importedFqNameString as $alias\")\n          .build()\n      }\n\n      override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression) {\n        wrongAlias?.let {\n          if (node.asSourceString() == it) {\n            fixes.add(createReferenceLintFix(node))\n          }\n        }\n      }\n\n      private fun createReferenceLintFix(node: USimpleNameReferenceExpression): LintFix {\n        return fix().replace().range(context.getLocation(node)).with(rootIssueData?.alias).build()\n      }\n    }\n  }\n\n  companion object {\n\n    val ISSUE: Issue =\n      Issue.create(\n          \"WrongResourceImportAlias\",\n          \"Wrong import alias for this R class.\",\n          \"R class import aliases should be consistent across the codebase. For example: \\n\" +\n            \"import slack.l10n.R as L10nR\\n\" +\n            \"import slack.uikit.R as UiKitR\",\n          Category.CORRECTNESS,\n          6,\n          Severity.ERROR,\n          sourceImplementation<WrongResourceImportAliasDetector>(),\n        )\n        .setOptions(listOf(IMPORT_ALIASES))\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/resources/model/RootIssueData.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.resources.model\n\nimport com.android.tools.lint.detector.api.Location\n\ndata class RootIssueData(val alias: String, val nameLocation: Location)\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/retrofit/RetrofitUsageDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.retrofit\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Location\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.intellij.psi.PsiTypes\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.sourcePsiElement\nimport slack.lint.util.removeNode\nimport slack.lint.util.safeReturnType\nimport slack.lint.util.sourceImplementation\n\n/**\n * A simple detector that validates basic Retrofit usage.\n * - Retrofit endpoints must be annotated with a retrofit method API unless they're an extension\n *   function or private.\n * - `@FormUrlEncoded` must use `@POST`, `@PUT`, or `@PATCH`.\n * - `@Body` parameter requires `@POST`, `@PUT`, or `@PATCH`.\n * - `@Field` parameters require it to be annotated with `@FormUrlEncoded`.\n * - Must return something other than [Unit].\n */\nclass RetrofitUsageDetector : Detector(), SourceCodeScanner {\n\n  override fun getApplicableUastTypes() = listOf(UMethod::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitMethod(node: UMethod) {\n        val httpAnnotation =\n          HTTP_ANNOTATIONS.firstNotNullOfOrNull { node.findAnnotation(it) } ?: return\n\n        val returnType = node.safeReturnType(context)\n        val isVoidOrUnitReturnType =\n          returnType == null ||\n            returnType == PsiTypes.voidType() ||\n            returnType.canonicalText == \"kotlin.Unit\"\n        if (isVoidOrUnitReturnType) {\n          val allowsUnitResult = node.hasAnnotation(\"slack.lint.annotations.AllowUnitResult\")\n          val isSuspend = context.evaluator.isSuspend(node)\n          if (!(isSuspend && allowsUnitResult)) {\n            node.report(\n              \"Retrofit endpoints should return something other than Unit/void.\",\n              context.getNameLocation(node),\n            )\n          }\n        }\n\n        val httpAnnotationFqnc = httpAnnotation.qualifiedName ?: return\n        val isBodyMethod = httpAnnotationFqnc in HTTP_BODY_ANNOTATIONS\n        val annotationsByFqcn = node.uAnnotations.associateBy { it.qualifiedName }\n\n        val isFormUrlEncoded = FQCN_FORM_ENCODED in annotationsByFqcn\n\n        if (isFormUrlEncoded && !isBodyMethod) {\n          node.report(\"@FormUrlEncoded requires @PUT, @POST, or @PATCH.\")\n          return\n        }\n\n        val isMultipart = FQCN_MULTIPART in annotationsByFqcn\n        if (isMultipart && !isBodyMethod) {\n          node.report(\"@Multipart requires @PUT, @POST, or @PATCH.\")\n          return\n        }\n\n        val hasPath =\n          (httpAnnotation.findDeclaredAttributeValue(\"value\")?.evaluate() as? String)?.isNotBlank()\n            ?: false\n\n        var hasBodyParam = false\n        var hasFieldParams = false\n        var hasPartParams = false\n        var hasUrlParam = false\n\n        for (parameter in node.uastParameters) {\n          if (parameter.hasAnnotation(FQCN_BODY)) {\n            if (!isBodyMethod) {\n              httpAnnotation.report(\"@Body param requires @PUT, @POST, or @PATCH.\")\n            } else if (hasBodyParam) {\n              parameter.report(\"Duplicate @Body param!.\")\n            } else {\n              hasBodyParam = true\n            }\n          } else if (\n            parameter.hasAnnotation(FQCN_FIELD) || parameter.hasAnnotation(FQCN_FIELD_MAP)\n          ) {\n            hasFieldParams = true\n            if (!isFormUrlEncoded) {\n              val currentText = node.text\n              node.report(\n                \"@Field(Map) param requires @FormUrlEncoded.\",\n                quickFixData =\n                  LintFix.create()\n                    .replace()\n                    .text(currentText)\n                    .with(\"@$FQCN_FORM_ENCODED\\n$currentText\")\n                    .autoFix()\n                    .build(),\n              )\n            }\n          } else if (parameter.hasAnnotation(FQCN_URL)) {\n            if (hasPath) {\n              httpAnnotation.report(\"@Url param should be used with an empty path.\")\n            } else {\n              hasUrlParam = true\n            }\n          } else if (parameter.hasAnnotation(FQCN_PART)) {\n            if (!isBodyMethod) {\n              httpAnnotation.report(\"@Part param requires @PUT, @POST, or @PATCH.\")\n            } else {\n              hasPartParams = true\n            }\n          }\n        }\n\n        if (isFormUrlEncoded) {\n          if (!hasFieldParams) {\n            val annotation = annotationsByFqcn.getValue(FQCN_FORM_ENCODED)\n            annotation.report(\n              \"@FormUrlEncoded but has no @Field(Map) parameters.\",\n              quickFixData = LintFix.create().removeNode(context, annotation.sourcePsiElement!!),\n            )\n          }\n        } else if (isMultipart) {\n          if (hasBodyParam || hasFieldParams) {\n            httpAnnotation.report(\"@Multipart methods should only contain @Part parameters.\")\n          } else if (!hasPartParams) {\n            httpAnnotation.report(\"@Multipart methods should contain at least one @Part parameter.\")\n          }\n        } else if (isBodyMethod && !hasBodyParam && !hasFieldParams && !hasPartParams) {\n          httpAnnotation.report(\"This annotation requires an `@Body` parameter.\")\n        }\n        if (!hasPath && !hasUrlParam) {\n          httpAnnotation.report(\"Http path is empty but has no @Url parameter.\")\n        }\n      }\n\n      private fun UElement.report(\n        briefDescription: String,\n        location: Location = context.getLocation(this),\n        quickFixData: LintFix? = null,\n      ) {\n        context.report(ISSUE, location, briefDescription, quickfixData = quickFixData)\n      }\n    }\n  }\n\n  companion object {\n    private val HTTP_ANNOTATIONS =\n      setOf(\n        \"retrofit2.http.DELETE\",\n        \"retrofit2.http.GET\",\n        \"retrofit2.http.HEAD\",\n        \"retrofit2.http.OPTIONS\",\n        \"retrofit2.http.PATCH\",\n        \"retrofit2.http.POST\",\n        \"retrofit2.http.PUT\",\n      )\n    private val HTTP_BODY_ANNOTATIONS =\n      setOf(\"retrofit2.http.PATCH\", \"retrofit2.http.POST\", \"retrofit2.http.PUT\")\n    private const val FQCN_FORM_ENCODED = \"retrofit2.http.FormUrlEncoded\"\n    private const val FQCN_MULTIPART = \"retrofit2.http.Multipart\"\n    private const val FQCN_FIELD = \"retrofit2.http.Field\"\n    private const val FQCN_PART = \"retrofit2.http.Part\"\n    private const val FQCN_FIELD_MAP = \"retrofit2.http.FieldMap\"\n    private const val FQCN_BODY = \"retrofit2.http.Body\"\n    private const val FQCN_URL = \"retrofit2.http.Url\"\n\n    val ISSUE: Issue =\n      Issue.create(\n        \"RetrofitUsage\",\n        \"This is replaced by the caller.\",\n        \"This linter reports various common configuration issues with Retrofit.\",\n        Category.CORRECTNESS,\n        10,\n        Severity.ERROR,\n        sourceImplementation<RetrofitUsageDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/rx/RxObservableEmitDetector.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.rx\n\nimport com.android.tools.lint.checks.DataFlowAnalyzer\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.intellij.psi.PsiClassType\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.ULambdaExpression\nimport slack.lint.util.sourceImplementation\n\nclass RxObservableEmitDetector : Detector(), SourceCodeScanner {\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitCallExpression(node: UCallExpression) {\n        val issue = functionToIssue[node.methodName] ?: return\n        val lambdaExpression = node.valueArguments.lastOrNull() as? ULambdaExpression ?: return\n        val producerScopeParam = lambdaExpression.parameters.firstOrNull() ?: return\n        val producerScopeType = (producerScopeParam.type as? PsiClassType) ?: return\n\n        // Verify the parameter is a ProducerScope\n        if (producerScopeType.resolve()?.qualifiedName != PROVIDER_SCOPE_FQN) return\n\n        var sendCalled = false\n\n        val visitor =\n          object : DataFlowAnalyzer(emptySet()) {\n            override fun visitCallExpression(node: UCallExpression): Boolean {\n              // If we find a nested factory method, return immediately and stop traversing this\n              // code path.\n              // Note: this factory will be validated by the UElementHandler above\n              if (node.methodName in functionToIssue) return true\n\n              if (node.hasProviderScopeReceiver() && node.methodName in REQUIRE_ONE_OF) {\n                sendCalled = true\n              }\n\n              return false\n            }\n          }\n\n        lambdaExpression.accept(visitor)\n\n        if (!sendCalled) {\n          context.report(\n            issue,\n            context.getLocation(node),\n            \"${node.methodName} does not call `send()` or `trySend()`\",\n          )\n        }\n      }\n    }\n  }\n\n  private fun UCallExpression.hasProviderScopeReceiver(): Boolean =\n    receiverType?.canonicalText?.startsWith(PROVIDER_SCOPE_FQN) == true\n\n  internal companion object {\n    private val REQUIRE_ONE_OF = setOf(\"send\", \"trySend\")\n    private const val PROVIDER_SCOPE_FQN = \"kotlin.coroutines.ProducerScope\"\n\n    private val ISSUE_RX_OBSERVABLE_DOES_NOT_EMIT =\n      Issue.create(\n        id = \"RxObservableDoesNotEmit\",\n        briefDescription = \"RxObservable should call `send()` or `trySend()`\",\n        explanation =\n          \"If the rxObservable trailing lambda does not call `send()` or `trySend()`, the returned Observable will never emit!\",\n        category = Category.CORRECTNESS,\n        priority = 2,\n        severity = Severity.INFORMATIONAL,\n        implementation = sourceImplementation<RxObservableEmitDetector>(),\n      )\n\n    private val ISSUE_RX_FLOWABLE_DOES_NOT_EMIT =\n      Issue.create(\n        id = \"RxFlowableDoesNotEmit\",\n        briefDescription = \"RxFlowable should call `send()` or `trySend()`\",\n        explanation =\n          \"If the rxFlowable trailing lambda does not call `send()` or `trySend()`, the returned Flowable will never emit!\",\n        category = Category.CORRECTNESS,\n        priority = 2,\n        severity = Severity.INFORMATIONAL,\n        implementation = sourceImplementation<RxObservableEmitDetector>(),\n      )\n\n    val issues = listOf(ISSUE_RX_OBSERVABLE_DOES_NOT_EMIT, ISSUE_RX_FLOWABLE_DOES_NOT_EMIT)\n\n    private val functionToIssue =\n      mapOf(\n        \"rxObservable\" to ISSUE_RX_OBSERVABLE_DOES_NOT_EMIT,\n        \"rxFlowable\" to ISSUE_RX_FLOWABLE_DOES_NOT_EMIT,\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/rx/RxSubscribeOnMainDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.rx\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.intellij.lang.java.JavaLanguage\nimport com.intellij.psi.PsiMethod\nimport com.intellij.psi.PsiVariable\nimport kotlin.reflect.full.safeCast\nimport org.jetbrains.kotlin.asJava.elements.KtLightField\nimport org.jetbrains.kotlin.idea.KotlinLanguage\nimport org.jetbrains.kotlin.psi.KtProperty\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.UExpression\nimport org.jetbrains.uast.UQualifiedReferenceExpression\nimport org.jetbrains.uast.UastFacade\nimport org.jetbrains.uast.java.JavaUCallExpression\nimport org.jetbrains.uast.java.JavaUCompositeQualifiedExpression\nimport org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression\nimport org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression\nimport org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression\nimport org.jetbrains.uast.kotlin.psi.UastKotlinPsiVariable\nimport slack.lint.util.sourceImplementation\n\n/**\n * [Detector] for usages of `Observable.subscribeOn(AndroidSchedulers.mainThread())`. Typically,\n * this is not desired and instead users are looking for\n * `observeOn(AndroidSchedulers.mainThread())`.\n */\nclass RxSubscribeOnMainDetector : Detector(), SourceCodeScanner {\n\n  companion object {\n    private fun Implementation.toIssue() =\n      Issue.create(\n        id = \"SubscribeOnMain\",\n        briefDescription = \"subscribeOn called with the main thread scheduler.\",\n        explanation =\n          \"\"\"\n        Calling `subscribeOn(AndroidSchedulers.mainThread())` will cause the code ran at subscription time to be executed \\\n        on the main thread - that is, code above this line.\n        Typically this is not actually desired, and instead you want to use observeOn(AndroidSchedulers.mainThread()) \\\n        which will cause the code below this line to be run on the main thread (eg the code inside your subscribe() \\\n        block).\n      \"\"\",\n        category = Category.CORRECTNESS,\n        priority = 4,\n        severity = Severity.ERROR,\n        implementation = this,\n      )\n\n    val ISSUE = sourceImplementation<RxSubscribeOnMainDetector>().toIssue()\n  }\n\n  override fun getApplicableMethodNames(): List<String> = listOf(\"subscribeOn\")\n\n  override fun getApplicableCallOwners() =\n    listOf(\n      \"io/reactivex/rxjava3/core/Completable\",\n      \"io/reactivex/rxjava3/core/Flowable\",\n      \"io/reactivex/rxjava3/core/Maybe\",\n      \"io/reactivex/rxjava3/core/Observable\",\n      \"io/reactivex/rxjava3/core/Single\",\n    )\n\n  override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {\n    val arg = node.valueArguments.first()\n    when (arg) {\n      is JavaUCompositeQualifiedExpression ->\n        checkCall { JavaUCallExpression::class.safeCast(arg.selector) }\n      is JavaUCallExpression -> checkCall { arg }\n      is KotlinUQualifiedReferenceExpression ->\n        checkCall { KotlinUFunctionCallExpression::class.safeCast(arg.selector) }\n      is KotlinUFunctionCallExpression -> checkCall { arg }\n      else -> checkVariable { arg }\n    }.let { mainThreadFound ->\n      if (mainThreadFound) {\n        context.report(\n          ISSUE,\n          context.getCallLocation(node, includeReceiver = false, includeArguments = true),\n          \"This will make the code for the initial subscription (above this line) run on the main thread. \" +\n            \"You probably want `observeOn(AndroidSchedulers.mainThread())`.\",\n          LintFix.create()\n            .replace()\n            .name(\"Replace with observeOn()\")\n            .text(\"subscribeOn\")\n            .with(\"observeOn\")\n            .build(),\n        )\n      }\n    }\n  }\n\n  /**\n   * return true if the resolved [UCallExpression] has method name \"mainThread\" or\n   * \"immediateMainThread\", false otherwise\n   */\n  private fun checkCall(fn: () -> UCallExpression?): Boolean {\n    return fn()?.let { call ->\n      \"mainThread\" == call.methodName || \"immediateMainThread\" == call.methodName\n    } ?: false\n  }\n\n  /**\n   * return true if the resolved [UExpression] was created from the \"mainThread\" or\n   * \"immediateMainThread\" methods, false otherwise\n   */\n  private fun checkVariable(fn: () -> UExpression?): Boolean {\n    return fn()?.let { exp ->\n      when (exp.lang) {\n        is KotlinLanguage -> checkKotlinVariable(exp)\n        is JavaLanguage -> checkJavaVariable(exp)\n        else -> return false\n      }\n    } ?: false\n  }\n\n  private fun checkKotlinVariable(exp: UExpression): Boolean {\n    return when (exp) {\n      is KotlinUSimpleReferenceExpression -> {\n        val initializerText =\n          when (val reference = exp.resolve()) {\n            is KtLightField -> { // The variable reference is a member\n              val initializer = (reference.kotlinOrigin as? KtProperty)?.initializer\n              initializer?.node?.text\n            }\n            is UastKotlinPsiVariable -> { // The variable reference is local\n              val initializer = reference.initializer\n              initializer?.node?.text\n            }\n            else -> null\n          }\n        initializerText?.let { it.endsWith(\"mainThread()\") || it.endsWith(\"immediateMainThread()\") }\n          ?: false\n      }\n      else -> false\n    }\n  }\n\n  private fun checkJavaVariable(exp: UExpression): Boolean {\n    val assignment: UCallExpression? =\n      when (val variable = exp.sourcePsi?.reference?.resolve()) {\n        // PsiVariable covers both PsiField and PsiLocalVariable\n        is PsiVariable -> {\n          ((UastFacade.getInitializerBody(variable) as? UQualifiedReferenceExpression)?.selector\n            as? UCallExpression)\n        }\n        else -> null\n      }\n    val methodName = assignment?.resolve()?.name\n    return methodName == \"mainThread\" || methodName == \"immediateMainThread\"\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/text/SpanMarkPointMissingMaskDetector.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.text\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.UastLintUtils\nimport org.jetbrains.uast.UBinaryExpression\nimport org.jetbrains.uast.UExpression\nimport org.jetbrains.uast.UReferenceExpression\nimport org.jetbrains.uast.UastBinaryOperator\nimport org.jetbrains.uast.tryResolve\nimport slack.lint.text.SpanMarkPointMissingMaskDetector.Companion.ISSUE\nimport slack.lint.util.resolveQualifiedNameOrNull\nimport slack.lint.util.sourceImplementation\n\n/** Checks for SpanMarkPointMissingMask. See [ISSUE]. */\nclass SpanMarkPointMissingMaskDetector : Detector(), SourceCodeScanner {\n\n  companion object {\n    val ISSUE =\n      Issue.create(\n        id = \"SpanMarkPointMissingMask\",\n        briefDescription =\n          \"Check that Span flags use the bitwise mask SPAN_POINT_MARK_MASK when being compared to.\",\n        explanation =\n          \"\"\"\n        Spans flags can have priority or other bits set. \\\n        Ensure that Span flags are checked using \\\n        `currentFlag and Spanned.SPAN_POINT_MARK_MASK == desiredFlag` \\\n        rather than just `currentFlag == desiredFlag`\n      \"\"\",\n        category = Category.CORRECTNESS,\n        priority = 4,\n        severity = Severity.ERROR,\n        implementation = sourceImplementation<SpanMarkPointMissingMaskDetector>(),\n      )\n  }\n\n  override fun getApplicableUastTypes() = listOf(UBinaryExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler = ReportingHandler(context)\n}\n\nprivate const val SPANNED_CLASS = \"android.text.Spanned\"\nprivate val MARK_POINT_FIELDS =\n  setOf(\n    \"$SPANNED_CLASS.SPAN_INCLUSIVE_INCLUSIVE\",\n    \"$SPANNED_CLASS.SPAN_INCLUSIVE_EXCLUSIVE\",\n    \"$SPANNED_CLASS.SPAN_EXCLUSIVE_INCLUSIVE\",\n    \"$SPANNED_CLASS.SPAN_EXCLUSIVE_EXCLUSIVE\",\n  )\nprivate const val MASK_CLASS = \"$SPANNED_CLASS.SPAN_POINT_MARK_MASK\"\n\n/** Reports violations of SpanMarkPointMissingMask. */\nprivate class ReportingHandler(private val context: JavaContext) : UElementHandler() {\n  override fun visitBinaryExpression(node: UBinaryExpression) {\n    if (\n      node.operator == UastBinaryOperator.EQUALS ||\n        node.operator == UastBinaryOperator.NOT_EQUALS ||\n        node.operator == UastBinaryOperator.IDENTITY_EQUALS ||\n        node.operator == UastBinaryOperator.IDENTITY_NOT_EQUALS\n    ) {\n      checkExpressions(node, node.leftOperand, node.rightOperand)\n      checkExpressions(node, node.rightOperand, node.leftOperand)\n    }\n  }\n\n  private fun checkExpressions(\n    node: UBinaryExpression,\n    markPointCheck: UExpression,\n    maskCheck: UExpression,\n  ) {\n    if (markPointCheck.isMarkPointFieldName() && !maskCheck.isMaskClass()) {\n      context.report(\n        ISSUE,\n        context.getLocation(node),\n        \"\"\"\n          Do not check against ${markPointCheck.sourcePsi?.text} directly. \\\n          Instead mask flag with Spanned.SPAN_POINT_MARK_MASK to only check MARK_POINT flags.\n        \"\"\"\n          .trimIndent(),\n        LintFix.create()\n          .replace()\n          .name(\"Use bitwise mask\")\n          .text(maskCheck.sourcePsi?.text)\n          .with(\"((${maskCheck.sourcePsi?.text}) and $MASK_CLASS)\")\n          .build(),\n      )\n    }\n  }\n}\n\nprivate fun UExpression.isMarkPointFieldName(): Boolean =\n  this.getQualifiedName() in MARK_POINT_FIELDS\n\nprivate fun UExpression.getQualifiedName(): String? {\n  return (this as? UReferenceExpression)\n    ?.referenceNameElement\n    ?.uastParent\n    ?.tryResolve()\n    ?.let(UastLintUtils::getQualifiedName)\n}\n\nprivate fun UExpression.isMaskClass(): Boolean {\n  return if (this is UBinaryExpression) {\n    this.leftOperand.resolveQualifiedNameOrNull() == MASK_CLASS ||\n      this.rightOperand.resolveQualifiedNameOrNull() == MASK_CLASS\n  } else {\n    false\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/ui/DoNotCallViewToString.kt",
    "content": "// Copyright (C) 2024 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.ui\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.intellij.psi.util.InheritanceUtil\nimport org.jetbrains.uast.UCallExpression\nimport slack.lint.util.sourceImplementation\n\nclass DoNotCallViewToString : Detector(), SourceCodeScanner {\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler {\n    return object : UElementHandler() {\n      override fun visitCallExpression(node: UCallExpression) {\n        if (node.methodName != \"toString\") return\n        val method = node.resolve() ?: return\n        val containingClass = method.containingClass ?: return\n        if (InheritanceUtil.isInheritor(containingClass, \"android.view.View\")) {\n          context.report(\n            ISSUE,\n            node,\n            context.getNameLocation(node),\n            \"Do not call `View.toString()`\",\n          )\n        }\n      }\n    }\n  }\n\n  companion object {\n    val ISSUE: Issue =\n      Issue.Companion.create(\n        \"DoNotCallViewToString\",\n        \"Do not use `View.toString()`\",\n        \"\"\"\n        `View.toString()` and its overrides can often print surprisingly detailed information about \\\n        the current view state, and has led to PII logging issues in the past.\n      \"\"\",\n        Category.Companion.SECURITY,\n        9,\n        Severity.ERROR,\n        sourceImplementation<DoNotCallViewToString>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/ui/ItemDecorationViewBindingDetector.kt",
    "content": "// Copyright (C) 2024 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.ui\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.intellij.psi.PsiClass\nimport com.intellij.psi.util.InheritanceUtil.isInheritor\nimport kotlin.jvm.java\nimport org.jetbrains.uast.UCallExpression\nimport org.jetbrains.uast.getContainingUClass\nimport slack.lint.util.implements\nimport slack.lint.util.sourceImplementation\n\n/**\n * Lint detector that ensures `ItemDecoration` does not inflate a view. If the view contains\n * `TextView`, this textual information cannot be announced to screen readers by TalkBack.\n */\nclass ItemDecorationViewBindingDetector : Detector(), SourceCodeScanner {\n  override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)\n\n  override fun createUastHandler(context: JavaContext): UElementHandler =\n    object : UElementHandler() {\n\n      override fun visitCallExpression(node: UCallExpression) {\n        val containingClass = node.getContainingUClass() ?: return\n        if (!containingClass.implements(ITEM_DECORATION_CLASS_NAME)) return\n\n        val method = node.resolve() ?: return\n        val methodClass = method.containingClass\n\n        if (\n          LAYOUT_INFLATER_METHOD_NAME == method.name && methodClass?.isViewBindingClass() == true\n        ) {\n          context.report(ISSUE, node, context.getNameLocation(node), ISSUE_DESCRIPTION)\n        }\n      }\n    }\n\n  private fun PsiClass.isViewBindingClass(): Boolean {\n    return LAYOUT_INFLATER_PACKAGE_NAME == this.qualifiedName ||\n      isInheritor(this, VIEW_BINDING_PACKAGE_NAME)\n  }\n\n  companion object {\n    private const val ISSUE_ID = \"InflationInItemDecoration\"\n    private const val ITEM_DECORATION_CLASS_NAME =\n      \"androidx.recyclerview.widget.RecyclerView.ItemDecoration\"\n    private const val LAYOUT_INFLATER_METHOD_NAME = \"inflate\"\n    private const val LAYOUT_INFLATER_PACKAGE_NAME = \"android.view.LayoutInflater\"\n    private const val VIEW_BINDING_PACKAGE_NAME = \"androidx.viewbinding.ViewBinding\"\n\n    private const val ISSUE_BRIEF_DESCRIPTION = \"Avoid inflating a view to display text\"\n    private const val ISSUE_DESCRIPTION =\n      \"\"\"\n        ViewBinding should not be used in `ItemDecoration`. If an inflated view contains \\\n        meaningful textual information, it will not be visible to TalkBack. This means \\\n        that screen reader users will not be able to know what is on the screen.\n      \"\"\"\n\n    val ISSUE =\n      Issue.create(\n        id = ISSUE_ID,\n        briefDescription = ISSUE_BRIEF_DESCRIPTION,\n        explanation = ISSUE_DESCRIPTION,\n        category = Category.A11Y,\n        priority = 10,\n        severity = Severity.WARNING,\n        implementation = sourceImplementation<ItemDecorationViewBindingDetector>(),\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/util/BooleanLintOption.kt",
    "content": "// Copyright (C) 2023 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.util\n\nimport com.android.tools.lint.client.api.Configuration\nimport com.android.tools.lint.detector.api.BooleanOption\n\nopen class BooleanLintOption(private val option: BooleanOption) : LintOption {\n  var value: Boolean = option.defaultValue\n    private set\n\n  override fun load(configuration: Configuration) {\n    value = option.getValue(configuration)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/util/LintOption.kt",
    "content": "// Copyright (C) 2023 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.util\n\nimport com.android.tools.lint.client.api.Configuration\n\n/**\n * A layer of indirection for implementations of option loaders without needing to extend from\n * Detector. This goes along with [OptionLoadingDetector].\n */\ninterface LintOption {\n  fun load(configuration: Configuration)\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/util/LintUtils.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.util\n\nimport com.android.tools.lint.client.api.Configuration\nimport com.android.tools.lint.client.api.JavaEvaluator\nimport com.android.tools.lint.client.api.TYPE_BOOLEAN_WRAPPER\nimport com.android.tools.lint.client.api.TYPE_BYTE_WRAPPER\nimport com.android.tools.lint.client.api.TYPE_CHARACTER_WRAPPER\nimport com.android.tools.lint.client.api.TYPE_DOUBLE_WRAPPER\nimport com.android.tools.lint.client.api.TYPE_FLOAT_WRAPPER\nimport com.android.tools.lint.client.api.TYPE_INTEGER_WRAPPER\nimport com.android.tools.lint.client.api.TYPE_LONG_WRAPPER\nimport com.android.tools.lint.client.api.TYPE_SHORT_WRAPPER\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.LintFix\nimport com.android.tools.lint.detector.api.ResourceXmlDetector\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport com.android.tools.lint.detector.api.StringOption\nimport com.android.tools.lint.detector.api.UastLintUtils\nimport com.android.tools.lint.detector.api.isKotlin\nimport com.intellij.psi.PsiClass\nimport com.intellij.psi.PsiClassType\nimport com.intellij.psi.PsiElement\nimport com.intellij.psi.PsiJavaFile\nimport com.intellij.psi.PsiMethod\nimport com.intellij.psi.PsiModifierListOwner\nimport com.intellij.psi.PsiType\nimport com.intellij.psi.PsiTypes\nimport com.intellij.psi.PsiWildcardType\nimport java.util.EnumSet\nimport org.jetbrains.kotlin.idea.KotlinLanguage\nimport org.jetbrains.kotlin.lexer.KtTokens\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UExpression\nimport org.jetbrains.uast.UMethod\nimport org.jetbrains.uast.UQualifiedReferenceExpression\nimport org.jetbrains.uast.UReferenceExpression\nimport org.jetbrains.uast.USimpleNameReferenceExpression\nimport org.jetbrains.uast.tryResolve\n\n/**\n * @param qualifiedName the qualified name of the desired interface type\n * @param nameFilter an optional name filter, used to check when to stop searching up the type\n *   hierarchy. This is useful if you want to only check direct implementers in certain packages.\n *   Called with a fully qualified class name; return false if you want to stop searching up the\n *   type tree, true to continue.\n */\ninternal fun PsiClass.implements(\n  qualifiedName: String,\n  nameFilter: (String) -> Boolean = { true },\n): Boolean {\n  val fqcn = this.qualifiedName ?: return false\n  if (fqcn == qualifiedName) {\n    // Found a match\n    return true\n  }\n\n  if (!nameFilter(fqcn)) {\n    // Don't proceed further\n    return false\n  }\n\n  return this.superTypes.filterNotNull().any { classType ->\n    classType.resolve()?.implements(qualifiedName, nameFilter) ?: false\n  }\n}\n\n/** @return whether [owner] is a Kotlin `value` class. */\ninternal fun JavaEvaluator.isValueClass(owner: PsiModifierListOwner?): Boolean {\n  // Check the annotation for JvmInline as a shorter check\n  return owner?.hasAnnotation(\"kotlin.jvm.JvmInline\") == true ||\n    hasModifier(owner, KtTokens.VALUE_KEYWORD)\n}\n\ninternal fun UClass.isInnerClass(evaluator: JavaEvaluator): Boolean {\n  // If it has no containing class, it's top-level and therefore not inner\n  containingClass ?: return false\n\n  // If it's static (i.e. in Java), it's not an inner class\n  if (isStatic) return false\n\n  // If it's Kotlin and \"inner\", then it's definitely an inner class\n  if (isKotlin(language) && evaluator.hasModifier(this, KtTokens.INNER_KEYWORD)) return true\n\n  // We could check the containing class's innerClasses to look for a match here, but we've\n  // logically ruled\n  // out this possibility above\n  return false\n}\n\n@Suppress(\"SpreadOperator\")\ninternal inline fun <reified T> sourceImplementation(\n  shouldRunOnTestSources: Boolean = true\n): Implementation where T : Detector, T : SourceCodeScanner {\n  // We use the overloaded constructor that takes a varargs of `Scope` as the last param.\n  // This is to enable on-the-fly IDE checks. We are telling lint to run on both\n  // JAVA and TEST_SOURCES in the `scope` parameter but by providing the `analysisScopes`\n  // params, we're indicating that this check can run on either JAVA or TEST_SOURCES and\n  // doesn't require both of them together.\n  // From discussion on lint-dev https://groups.google.com/d/msg/lint-dev/ULQMzW1ZlP0/1dG4Vj3-AQAJ\n  // This was supposed to be fixed in AS 3.4 but still required as recently as 3.6-alpha10.\n  return if (shouldRunOnTestSources) {\n    Implementation(\n      T::class.java,\n      EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES),\n      EnumSet.of(Scope.JAVA_FILE),\n      EnumSet.of(Scope.TEST_SOURCES),\n    )\n  } else {\n    Implementation(T::class.java, EnumSet.of(Scope.JAVA_FILE))\n  }\n}\n\n@Suppress(\"SpreadOperator\")\ninternal inline fun <reified T : ResourceXmlDetector> resourcesImplementation(): Implementation {\n  return Implementation(T::class.java, Scope.RESOURCE_FILE_SCOPE)\n}\n\n/** Removes a given [node] as a fix. */\ninternal fun LintFix.Builder.removeNode(\n  context: JavaContext,\n  node: PsiElement,\n  name: String? = null,\n  autoFix: Boolean = true,\n  text: String = node.text,\n): LintFix {\n  val fixName = name ?: \"Remove '$text'\"\n  return replace()\n    .name(fixName)\n    .range(context.getLocation(node))\n    .shortenNames()\n    .text(text)\n    .with(\"\")\n    .apply {\n      if (autoFix) {\n        autoFix()\n      }\n    }\n    .build()\n}\n\ninternal fun String.snakeToCamel(): String {\n  return buildString {\n    var capNext = false\n    var letterSeen = false\n    for (c in this@snakeToCamel) {\n      if (c == '_' || c == '-') {\n        capNext = letterSeen\n        continue\n      } else {\n        letterSeen = true\n        if (capNext) {\n          append(c.uppercaseChar())\n          capNext = false\n        } else {\n          append(c)\n        }\n      }\n    }\n  }\n}\n\n@Suppress(\"ComplexCondition\")\ninternal fun String.toScreamingSnakeCase(): String {\n  return buildString {\n    var prevWasLower = false\n    var hasPendingUnderScore = false\n    var letterSeen = false\n    for (c in this@toScreamingSnakeCase) {\n      if (c == '_') {\n        if (letterSeen) {\n          hasPendingUnderScore = true\n        }\n        continue\n      } else if (c == '-' || c == '.' || c == ':' || c == '/') {\n        // Wild west characters in enum member names\n        // TODO maybe we should report these\n        if (letterSeen) {\n          hasPendingUnderScore = true\n        }\n      } else {\n        letterSeen = true\n        if (hasPendingUnderScore) {\n          append('_')\n        }\n        hasPendingUnderScore = false\n        if (c.isUpperCase()) {\n          if (prevWasLower) {\n            prevWasLower = false\n            append('_')\n          }\n          append(c)\n        } else {\n          prevWasLower = true\n          append(c.uppercaseChar())\n        }\n      }\n    }\n  }\n}\n\n/** List of platform types that Moshi's reflective adapter refuses. From ClassJsonAdapter. */\nprivate val PLATFORM_PACKAGES =\n  setOf(\"android\", \"androidx\", \"java\", \"javax\", \"kotlin\", \"kotlinx\", \"scala\")\n\nprivate val BOXED_PRIMITIVES =\n  setOf(\n    TYPE_INTEGER_WRAPPER,\n    TYPE_BOOLEAN_WRAPPER,\n    TYPE_BYTE_WRAPPER,\n    TYPE_SHORT_WRAPPER,\n    TYPE_LONG_WRAPPER,\n    TYPE_DOUBLE_WRAPPER,\n    TYPE_FLOAT_WRAPPER,\n    TYPE_CHARACTER_WRAPPER,\n  )\n\ninternal fun PsiClass.isBoxedPrimitive(): Boolean {\n  val fqcn = qualifiedName ?: return false\n  return fqcn in BOXED_PRIMITIVES\n}\n\ninternal fun PsiClass.isString(): Boolean {\n  val fqcn = qualifiedName ?: return false\n  return fqcn == \"java.lang.String\"\n}\n\ninternal fun PsiClass.isObjectOrAny(): Boolean {\n  val fqcn = qualifiedName ?: return false\n  return fqcn == \"java.lang.Object\"\n}\n\ninternal fun PsiClass.isPlatformType(): Boolean {\n  val fqcn = qualifiedName ?: return false\n  val firstPackagePart = fqcn.substringBefore(\".\")\n  return firstPackagePart in PLATFORM_PACKAGES\n}\n\n/**\n * Given reference expressions, try to unwrap the simple name expression (useful if the reference is\n * always the same type, like an enum).\n *\n * `Foo.BAR` -> BAR `BAR` -> BAR\n */\ninternal fun UExpression.unwrapSimpleNameReferenceExpression(): USimpleNameReferenceExpression {\n  return when (this) {\n    is USimpleNameReferenceExpression -> this\n    is UQualifiedReferenceExpression -> this.selector.unwrapSimpleNameReferenceExpression()\n    else -> error(\"Unrecognized reference expression type $javaClass\")\n  }\n}\n\n/**\n * Returns the fully qualified name of the expression, or null if unknown.\n *\n * For example, given:\n * ```\n * import org.x.Clazz.CONSTANT\n * ...\n *     if (aVar == CONSTANT)\n *                 ^^^^^^^^\n * ```\n *\n * The qualified name of the underlined expression will be \"org.x.Clazz.CONSTANT\".\n */\ninternal fun UExpression.resolveQualifiedNameOrNull(): String? {\n  return (this as? UReferenceExpression)?.referenceNameElement?.uastParent?.tryResolve()?.let {\n    UastLintUtils.getQualifiedName(it)\n  }\n}\n\n/**\n * Collects the return type of this [UMethod] in a suspend-safe way.\n *\n * For coroutines, the suspend methods return context rather than the source-declared return type,\n * which is encoded in a continuation parameter at the end of the parameter list.\n *\n * For example, the following snippet:\n * ```\n * suspend fun foo(): String\n * ```\n *\n * Will appear like so to lint:\n * ```\n * Object foo(Continuation<? super String> continuation)\n * ```\n */\ninternal fun UMethod.safeReturnType(context: JavaContext): PsiType? {\n  if (language == KotlinLanguage.INSTANCE && context.evaluator.isSuspend(this)) {\n    val classReference = parameterList.parameters.lastOrNull()?.type as? PsiClassType ?: return null\n    val wildcard = classReference.parameters.singleOrNull() as? PsiWildcardType ?: return null\n    return wildcard.bound\n  } else {\n    return returnType\n  }\n}\n\n/** Loads a [StringOption] as a [delimiter]-delimited [Set] of strings. */\ninternal fun StringOption.loadAsSet(\n  configuration: Configuration,\n  delimiter: String = \",\",\n): Set<String> {\n  return getValue(configuration)\n    ?.splitToSequence(delimiter)\n    .orEmpty()\n    .map(String::trim)\n    .filter(String::isNotBlank)\n    .toSet()\n}\n\ninternal inline fun <T, reified R> Array<out T>.mapArray(transform: (T) -> R): Array<R> =\n  Array(this.size) { i -> transform(this[i]) }\n\ninternal inline fun <T> measureTimeMillisWithResult(block: () -> T): Pair<Long, T> {\n  val start = System.currentTimeMillis()\n  val result = block()\n  return Pair(System.currentTimeMillis() - start, result)\n}\n\nprivate val logVerbosely by lazy {\n  System.getProperty(\"slack.lint.logVerbosely\", \"false\").toBoolean()\n}\nprivate val logErrorsVerbosely by lazy {\n  System.getProperty(\"slack.lint.logErrorsVerbosely\", \"true\").toBoolean()\n}\n\n/**\n * Logs to std if [logVerbosely] is enabled. Useful for debugging and should not generally be\n * enabled.\n */\ninternal fun slackLintLog(message: String) {\n  if (logVerbosely) {\n    println(\"SlackLint: $message\")\n  }\n}\n\n/**\n * Logs to std if [logErrorsVerbosely] is enabled. Important for errors that you don't necessarily\n * want to fail the build\n */\ninternal fun slackLintErrorLog(message: String) {\n  if (logErrorsVerbosely) {\n    System.err.println(\"SlackLint: $message\")\n  }\n}\n\n/** Returns whether [this] has [packageName] as its package name. */\ninternal fun PsiMethod.isInPackageName(packageName: PackageName): Boolean {\n  val actual = (containingFile as? PsiJavaFile)?.packageName\n  return packageName.javaPackageName == actual\n}\n\n/** Whether this [PsiMethod] returns Unit */\ninternal val PsiMethod.returnsUnit\n  get() = returnType.isVoidOrUnit\n\n/**\n * Whether this [PsiType] is `void` or [Unit]\n *\n * In Kotlin 1.6 some expressions now explicitly return [Unit] instead of just being [PsiType.VOID],\n * so this returns whether this type is either.\n */\ninternal val PsiType?.isVoidOrUnit\n  get() = this == PsiTypes.voidType() || this?.canonicalText == \"kotlin.Unit\"\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/util/MetadataJavaEvaluator.kt",
    "content": "// Copyright (C) 2023 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.util\n\nimport com.android.tools.lint.client.api.JavaEvaluator\nimport com.android.tools.lint.model.LintModelDependencies\nimport com.intellij.lang.jvm.JvmClassKind\nimport com.intellij.psi.PsiAnnotation\nimport com.intellij.psi.PsiArrayInitializerMemberValue\nimport com.intellij.psi.PsiClass\nimport com.intellij.psi.PsiClassType\nimport com.intellij.psi.PsiCompiledElement\nimport com.intellij.psi.PsiElement\nimport com.intellij.psi.PsiLiteralExpression\nimport com.intellij.psi.PsiModifierListOwner\nimport com.intellij.psi.PsiPackage\nimport com.intellij.psi.PsiType\nimport java.util.Optional\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.jvm.optionals.getOrNull\nimport kotlin.metadata.ClassKind\nimport kotlin.metadata.KmClass\nimport kotlin.metadata.Modality\nimport kotlin.metadata.isData\nimport kotlin.metadata.isValue\nimport kotlin.metadata.jvm.JvmMetadataVersion\nimport kotlin.metadata.jvm.KotlinClassMetadata\nimport kotlin.metadata.jvm.Metadata as MetadataWithNullableArgs\nimport kotlin.metadata.kind\nimport kotlin.metadata.modality\nimport org.jetbrains.kotlin.lexer.KtModifierKeywordToken\nimport org.jetbrains.kotlin.lexer.KtTokens\nimport org.jetbrains.kotlin.psi.KtObjectDeclaration\nimport org.jetbrains.uast.UAnnotated\nimport org.jetbrains.uast.UAnnotation\nimport org.jetbrains.uast.UClass\nimport org.jetbrains.uast.UElement\nimport org.jetbrains.uast.UExpression\nimport org.jetbrains.uast.findContaining\nimport org.jetbrains.uast.toUElementOfType\n\n/**\n * A delegating [JavaEvaluator] that implements more comprehensive checks for Kotlin classes via\n * metadata annotations.\n *\n * This is important because, when `checkDependencies` is set to false, Lint detectors cannot see\n * Kotlin language features in externally-compiled elements. This means that constructs like `data\n * classes` or similar are not visible. Using kotlinx-metadata, we can parse the [Metadata]\n * annotations on the containing classes and read these language features from them.\n *\n * This is necessary due to https://issuetracker.google.com/issues/283654244.\n */\nclass MetadataJavaEvaluator(private val file: String, private val delegate: JavaEvaluator) :\n  JavaEvaluator() {\n\n  private companion object {\n    // Not an exhaustive list, but at least the ones we look at currently\n    private val KOTLIN_METADATA_TOKENS =\n      mapOf(\n        KtTokens.DATA_KEYWORD to TokenData { it.isData },\n        KtTokens.SEALED_KEYWORD to\n          TokenData(applicableClassKinds = setOf(JvmClassKind.CLASS, JvmClassKind.INTERFACE)) {\n            it.modality == Modality.SEALED\n          },\n        KtTokens.OBJECT_KEYWORD to TokenData { it.kind == ClassKind.OBJECT },\n        KtTokens.COMPANION_KEYWORD to TokenData { it.kind == ClassKind.COMPANION_OBJECT },\n        KtTokens.VALUE_KEYWORD to TokenData { it.isValue },\n      )\n  }\n\n  private data class TokenData(\n    val applicableClassKinds: Set<JvmClassKind> = setOf(JvmClassKind.CLASS),\n    val isApplicable: (KmClass) -> Boolean,\n  )\n\n  /** Flag to disable as needed. */\n  private val checkMetadata = System.getProperty(\"slack.lint.checkMetadata\", \"true\").toBoolean()\n  private val cachedClasses = ConcurrentHashMap<String, Optional<KmClass>>()\n\n  // region Delegating functions\n  override val dependencies: LintModelDependencies?\n    get() = delegate.dependencies\n\n  override fun extendsClass(cls: PsiClass?, className: String, strict: Boolean): Boolean =\n    delegate.extendsClass(cls, className, strict)\n\n  @Suppress(\"DEPRECATION\")\n  @Deprecated(\n    \"Use getAnnotation returning a UAnnotation instead\",\n    replaceWith = ReplaceWith(\"getAnnotation(listOwner, *annotationNames)\"),\n  )\n  override fun findAnnotation(\n    listOwner: PsiModifierListOwner?,\n    vararg annotationNames: String,\n  ): PsiAnnotation? = delegate.findAnnotation(listOwner, *annotationNames)\n\n  @Suppress(\"DEPRECATION\")\n  @Deprecated(\n    \"Use getAnnotationInHierarchy returning a UAnnotation instead\",\n    replaceWith = ReplaceWith(\"getAnnotationInHierarchy(listOwner, *annotationNames)\"),\n  )\n  override fun findAnnotationInHierarchy(\n    listOwner: PsiModifierListOwner,\n    vararg annotationNames: String,\n  ): PsiAnnotation? = delegate.findAnnotationInHierarchy(listOwner, *annotationNames)\n\n  override fun findClass(qualifiedName: String): PsiClass? = delegate.findClass(qualifiedName)\n\n  override fun findJarPath(element: PsiElement): String? = delegate.findJarPath(element)\n\n  override fun findJarPath(element: UElement): String? = delegate.findJarPath(element)\n\n  @Suppress(\"DEPRECATION\")\n  @Deprecated(\n    \"Use getAnnotations() instead; consider providing a parent\",\n    replaceWith = ReplaceWith(\"getAnnotations(owner, inHierarchy)\"),\n  )\n  override fun getAllAnnotations(\n    owner: PsiModifierListOwner,\n    inHierarchy: Boolean,\n  ): Array<PsiAnnotation> = delegate.getAllAnnotations(owner, inHierarchy)\n\n  override fun getAllAnnotations(owner: UAnnotated, inHierarchy: Boolean): List<UAnnotation> =\n    delegate.getAllAnnotations(owner, inHierarchy)\n\n  override fun getAnnotation(\n    listOwner: PsiModifierListOwner?,\n    vararg annotationNames: String,\n  ): UAnnotation? = delegate.getAnnotation(listOwner, *annotationNames)\n\n  override fun getAnnotationInHierarchy(\n    listOwner: PsiModifierListOwner,\n    vararg annotationNames: String,\n  ): UAnnotation? = delegate.getAnnotationInHierarchy(listOwner, *annotationNames)\n\n  override fun getAnnotations(\n    owner: PsiModifierListOwner?,\n    inHierarchy: Boolean,\n    parent: UElement?,\n  ): List<UAnnotation> = delegate.getAnnotations(owner, inHierarchy, parent)\n\n  override fun getClassType(psiClass: PsiClass?): PsiClassType? = delegate.getClassType(psiClass)\n\n  override fun getPackage(node: PsiElement): PsiPackage? = delegate.getPackage(node)\n\n  override fun getPackage(node: UElement): PsiPackage? = delegate.getPackage(node)\n\n  override fun getTypeClass(psiType: PsiType?): PsiClass? = delegate.getTypeClass(psiType)\n\n  override fun implementsInterface(cls: PsiClass, interfaceName: String, strict: Boolean): Boolean =\n    delegate.implementsInterface(cls, interfaceName, strict)\n\n  // endregion\n\n  /** Deep isObject check that checks if the given [cls] is an `object` class. */\n  fun isObject(cls: PsiClass?): Boolean {\n    if (cls == null) return false\n\n    (cls as? UClass ?: cls.toUElementOfType<UClass>())?.let { uClass ->\n      if (uClass.sourcePsi is KtObjectDeclaration) {\n        return true\n      } else if (canCheckMetadata(cls)) {\n        val (applicableClassKinds, isApplicable) =\n          KOTLIN_METADATA_TOKENS.getValue(KtTokens.OBJECT_KEYWORD)\n        if (uClass.classKind in applicableClassKinds) {\n          uClass.getOrParseMetadata()?.let { kmClass ->\n            return isApplicable(kmClass)\n          }\n        }\n      }\n    }\n    return false\n  }\n\n  private fun canCheckMetadata(element: PsiElement): Boolean {\n    return checkMetadata && element is PsiCompiledElement\n  }\n\n  override fun hasModifier(owner: PsiModifierListOwner?, keyword: KtModifierKeywordToken): Boolean {\n    val superValue = super.hasModifier(owner, keyword)\n    // If it's not a compiled element or not a PsiClass, trust the super value and move on\n    if (owner !is PsiClass || !canCheckMetadata(owner)) {\n      return superValue\n    }\n\n    // We're working with an externally compiled element and it's a PsiClass, so we can do more\n    // thorough checks here.\n    KOTLIN_METADATA_TOKENS[keyword]?.let { (applicableClassKinds, isApplicable) ->\n      owner.findContaining(UClass::class.java)?.let { cls ->\n        // Only parse if the target class kind is applicable to the token we're checking. For\n        // example - when checking `data` tokens, they're not applicable to interfaces or enums.\n        if (cls.classKind in applicableClassKinds) {\n          cls.getOrParseMetadata()?.let { kmClass ->\n            return isApplicable(kmClass)\n          }\n        }\n      }\n    }\n\n    return superValue\n  }\n\n  private fun UAnnotated.getOrParseMetadata(): KmClass? {\n    val cls =\n      when (this) {\n        is UClass -> this\n        else -> return null // Only classes are supported right now\n      }\n    val fqcn =\n      qualifiedName\n        ?: run {\n          slackLintErrorLog(\"Qualified name is null for $cls in file $file\")\n          return null\n        }\n    return cachedClasses\n      // Don't use getOrPut. Kotlin's extension may still invoke the body and we don't want that\n      .computeIfAbsent(fqcn) { key ->\n        val annotation =\n          cls.findAnnotation(\"kotlin.Metadata\") ?: return@computeIfAbsent Optional.empty()\n        val (durationMillis, metadata) =\n          measureTimeMillisWithResult { annotation.parseMetadata(key) }\n        slackLintLog(\"Took ${durationMillis}ms to parse metadata for $key.\")\n        Optional.ofNullable(metadata)\n      }\n      .getOrNull()\n  }\n\n  private fun UAnnotation.parseMetadata(classNameHint: String): KmClass? {\n    val parsedMetadata =\n      try {\n        KotlinClassMetadata.readStrict(toMetadataAnnotation())\n      } catch (e: IllegalArgumentException) {\n        try {\n          KotlinClassMetadata.readLenient(toMetadataAnnotation()).also {\n            slackLintErrorLog(\n              \"Could not load metadata for $classNameHint from file $file with strict parsing. Using lenient parsing.\"\n            )\n          }\n        } catch (e: IllegalArgumentException) {\n          // Extremely weird case, log this specifically\n          slackLintErrorLog(\n            \"Could not load metadata for $classNameHint from file $file. This usually happens if the Kotlin version the class was compiled against is too new for lint to read (${JvmMetadataVersion.LATEST_STABLE_SUPPORTED}).\"\n          )\n          return null\n        }\n      }\n    return when (parsedMetadata) {\n      is KotlinClassMetadata.Class -> {\n        parsedMetadata.kmClass.also {\n          slackLintLog(\"Loaded KmClass for $classNameHint from file $file\")\n        }\n      }\n      else -> {\n        slackLintLog(\n          \"\"\"\n            Could not load KmClass for $classNameHint from file $file.\n            Metadata was $parsedMetadata\n          \"\"\"\n            .trimIndent()\n        )\n        null\n      }\n    }\n  }\n\n  private fun UAnnotation.toMetadataAnnotation(): Metadata {\n    return MetadataWithNullableArgs(\n      kind = findAttributeValue(\"k\")?.parseIntMember(),\n      metadataVersion = findAttributeValue(\"mv\")?.parseIntArray(),\n      data1 = findAttributeValue(\"d1\")?.parseStringArray(),\n      data2 = findAttributeValue(\"d2\")?.parseStringArray(),\n      extraString = findAttributeValue(\"xs\")?.parseStringMember(),\n      packageName = findAttributeValue(\"pn\")?.parseStringMember(),\n      extraInt = findAttributeValue(\"xi\")?.parseIntMember(),\n    )\n  }\n\n  private val PsiLiteralExpression.intValue: Int\n    get() = stringValue.toInt()\n\n  private val PsiLiteralExpression.stringValue: String\n    get() = value.toString()\n\n  private fun UExpression.parseIntMember() = (sourcePsi as PsiLiteralExpression).intValue\n\n  private fun UExpression.parseStringMember() = (sourcePsi as PsiLiteralExpression).stringValue\n\n  private fun UExpression.parseStringArray() =\n    (sourcePsi as PsiArrayInitializerMemberValue).initializers.mapArray { value ->\n      (value as PsiLiteralExpression).stringValue\n    }\n\n  private fun UExpression.parseIntArray(): IntArray {\n    val initializers = (sourcePsi as PsiArrayInitializerMemberValue).initializers\n    return IntArray(initializers.size) { index ->\n      (initializers[index] as PsiLiteralExpression).intValue\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/util/Names.kt",
    "content": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage slack.lint.util\n\nimport kotlin.metadata.ClassName\n\n/**\n * Represents a qualified package\n *\n * @property segments the segments representing the package\n */\ninternal class PackageName(val segments: List<String>) {\n  /** The Java-style package name for this [Name], separated with `.` */\n  val javaPackageName: String\n    get() = segments.joinToString(\".\")\n}\n\n/**\n * Represents the qualified name for an element\n *\n * @property pkg the package for this element\n * @property nameSegments the segments representing the element - there can be multiple in the case\n *   of nested classes.\n */\ninternal class Name(private val pkg: PackageName, private val nameSegments: List<String>) {\n  /** The short name for this [Name] */\n  val shortName: String\n    get() = nameSegments.last()\n\n  /** The Java-style fully qualified name for this [Name], separated with `.` */\n  val javaFqn: String\n    get() = pkg.segments.joinToString(\".\", postfix = \".\") + nameSegments.joinToString(\".\")\n\n  /**\n   * The [ClassName] for use with kotlinx.metadata. Note that in kotlinx.metadata the actual type\n   * might be different from the underlying JVM type, for example: kotlin/Int -> java/lang/Integer\n   */\n  val kmClassName: ClassName\n    get() = pkg.segments.joinToString(\"/\", postfix = \"/\") + nameSegments.joinToString(\".\")\n\n  /** The [PackageName] of this element. */\n  val packageName: PackageName\n    get() = pkg\n}\n\n/** @return a [PackageName] with a Java-style (separated with `.`) [packageName]. */\ninternal fun Package(packageName: String): PackageName = PackageName(packageName.split(\".\"))\n\n/** @return a [PackageName] with a Java-style (separated with `.`) [packageName]. */\ninternal fun Package(packageName: PackageName, shortName: String): PackageName =\n  PackageName(packageName.segments + shortName.split(\".\"))\n\n/** @return a [Name] with the provided [pkg] and Java-style (separated with `.`) [shortName]. */\ninternal fun Name(pkg: PackageName, shortName: String): Name = Name(pkg, shortName.split(\".\"))\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/util/OptionLoadingDetector.kt",
    "content": "// Copyright (C) 2023 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.util\n\nimport com.android.tools.lint.detector.api.Context\nimport com.android.tools.lint.detector.api.Detector\n\n/** A [Detector] that supports reading the given [options]. */\nabstract class OptionLoadingDetector(vararg options: LintOption) : Detector() {\n\n  private val options = options.toList()\n\n  override fun beforeCheckRootProject(context: Context) {\n    super.beforeCheckRootProject(context)\n    val config = context.configuration\n    options.forEach { it.load(config) }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/util/Priorities.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.util\n\n/**\n * Priorities with semantic names. Partially for readability, partially so detekt stops nagging\n * about MagicNumber.\n */\nobject Priorities {\n  const val HIGH = 10\n  const val NORMAL = 5\n  const val LOW = 3\n  const val NONE = 1\n}\n"
  },
  {
    "path": "slack-lint-checks/src/main/java/slack/lint/util/StringSetLintOption.kt",
    "content": "// Copyright (C) 2023 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.util\n\nimport com.android.tools.lint.client.api.Configuration\nimport com.android.tools.lint.detector.api.StringOption\n\nopen class StringSetLintOption(private val option: StringOption) : LintOption {\n  var value: Set<String> = emptySet()\n    private set\n\n  override fun load(configuration: Configuration) {\n    value = option.loadAsSet(configuration)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/AlwaysNullReadOnlyVariableDetectorTest.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport org.junit.Test\n\nclass AlwaysNullReadOnlyVariableDetectorTest : BaseSlackLintTest() {\n  override fun getDetector() = AlwaysNullReadOnlyVariableDetector()\n\n  override fun getIssues() = AlwaysNullReadOnlyVariableDetector.ISSUES.toList()\n\n  @Test\n  fun `initializing a read-only variable with null shows warnings`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            class Test {\n                val str: String? = null\n\n                fun method() {\n                    val strInFunction: String? = null\n                }\n            }\n            \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/Test.kt:2: Warning: Avoid initializing read-only variable with null in Kotlin [AvoidNullInitForReadOnlyVariables]\n            val str: String? = null\n                               ~~~~\n        src/Test.kt:5: Warning: Avoid initializing read-only variable with null in Kotlin [AvoidNullInitForReadOnlyVariables]\n                val strInFunction: String? = null\n                                             ~~~~\n        0 errors, 2 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `returning null in custom getter shows warnings`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            class Test {\n                val str1: String?\n                    get() = null\n\n                val str2: String?\n                    get() {\n                        return null\n                    }\n\n                val str3: String? = null\n                    get() = field\n            }\n            \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/Test.kt:10: Warning: Avoid initializing read-only variable with null in Kotlin [AvoidNullInitForReadOnlyVariables]\n            val str3: String? = null\n                                ~~~~\n        src/Test.kt:3: Warning: Avoid returning null in getter for read-only properties in Kotlin [AvoidReturningNullInGetter]\n                get() = null\n                        ~~~~\n        src/Test.kt:7: Warning: Avoid returning null in getter for read-only properties in Kotlin [AvoidReturningNullInGetter]\n                    return null\n                           ~~~~\n        0 errors, 3 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `initializing a read-write variable with null doesn't show warnings`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            class Test {\n                var str: String? = null\n\n                fun method() {\n                    var str: String = null\n                }\n            }\n            \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `initializing a read-only variable with not null doesn't show warnings`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            class Test {\n                val str: String? = \"str\"\n\n                fun method() {\n                    val str: String? = \"str\"\n                }\n            }\n            \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `initializing a read-write variable with not null doesn't show warnings`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            class Test {\n                var str: String? = \"str\"\n\n                fun method() {\n                    var str: String? = \"str\"\n                }\n            }\n            \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `returning not null in custom getter doesn't show warnings`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            class Test {\n                val str1: String?\n                    get() = \"str\"\n\n                val str2: String?\n                    get() {\n                        return \"str\"\n                    }\n            }\n            \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `parameter properties initialized to null are ok`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            class Test(val str1: String? = null)\n            \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `open properties initialized to null are ok`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            open class Test {\n              open val str1: String? = null\n            }\n            \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/ArgInFormattedQuantityStringResDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport org.junit.Test\n\nclass ArgInFormattedQuantityStringResDetectorTest : BaseSlackLintTest() {\n\n  override val skipTestModes: Array<TestMode> = arrayOf(TestMode.PARENTHESIZED)\n\n  override fun getDetector() = ArgInFormattedQuantityStringResDetector()\n\n  override fun getIssues() = ArgInFormattedQuantityStringResDetector.issues.toList()\n\n  @Test\n  fun getQuantityStringWithNoLocalizedFormatTest() {\n    lint()\n      .files(\n        java(\n          \"\" +\n            \"package com.slack.lint;\\n\" +\n            \"\\n\" +\n            \"import android.content.res.Resources;\\n\" +\n            \"\\n\" +\n            \"public class Foo {\\n\" +\n            \"  public void bar(Resources res) {\\n\" +\n            \"    String s = res.getQuantityString(0, 3, 3, \\\"asdf\\\");\\n\" +\n            \"  }\\n\" +\n            \"}\\n\"\n        )\n      )\n      .run()\n      .expect(\n        (\"src/com/slack/lint/Foo.java:7: Warning: This may require a localized count modifier. If so, use LocalizationUtils.getFormattedCount(). Consult #plz-localization if you are unsure. [ArgInFormattedQuantityStringRes]\\n\" +\n          \"    String s = res.getQuantityString(0, 3, 3, \\\"asdf\\\");\\n\" +\n          \"                                           ~\\n\" +\n          \"src/com/slack/lint/Foo.java:7: Warning: This may require a localized count modifier. If so, use LocalizationUtils.getFormattedCount(). Consult #plz-localization if you are unsure. [ArgInFormattedQuantityStringRes]\\n\" +\n          \"    String s = res.getQuantityString(0, 3, 3, \\\"asdf\\\");\\n\" +\n          \"                                              ~~~~~~\\n\" +\n          \"0 errors, 2 warnings\\n\")\n      )\n  }\n\n  @Test\n  fun getQuantityStringWithNoLocalizedFormatTestKotlin() {\n    lint()\n      .files(\n        kotlin(\n          \"\" +\n            \"package com.slack.lint\\n\" +\n            \"\\n\" +\n            \"import android.content.res.Resources\\n\" +\n            \"\\n\" +\n            \"class Foo {\\n\" +\n            \"  fun bar(res: Resources) {\\n\" +\n            \"    val s = res.getQuantityString(0, 3, 3, \\\"asdf\\\")\\n\" +\n            \"  }\\n\" +\n            \"}\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:7: Warning: This may require a localized count modifier. If so, use LocalizationUtils.getFormattedCount(). Consult #plz-localization if you are unsure. [ArgInFormattedQuantityStringRes]\n              val s = res.getQuantityString(0, 3, 3, \"asdf\")\n                                                  ~\n          src/com/slack/lint/Foo.kt:7: Warning: This may require a localized count modifier. If so, use LocalizationUtils.getFormattedCount(). Consult #plz-localization if you are unsure. [ArgInFormattedQuantityStringRes]\n              val s = res.getQuantityString(0, 3, 3, \"asdf\")\n                                                     ~~~~~~\n          0 errors, 2 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun getQuantityStringWithLocalizedFormatTest() {\n    lint()\n      .files(\n        java(\n          (\"\" +\n            \"package com.slack.lint;\\n\" +\n            \"import static com.slack.lint.Foo.LocalizationUtils.getFormattedCount;\\n\" +\n            \"\\n\" +\n            \"import android.content.res.Resources;\\n\" +\n            \"\\n\" +\n            \"public class Foo {\\n\" +\n            \"  public void bar(Resources res) {\\n\" +\n            \"    String s = res.getQuantityString(0, 3, getFormattedCount(), 3);\\n\" +\n            \"  }\\n\" +\n            \"  public static class LocalizationUtils {\\n\" +\n            \"   public static String getFormattedCount() { return \\\"\\\"; }\\n\" +\n            \"  }\" +\n            \"}\\n\")\n        )\n      )\n      .run()\n      .expect(\n        \"src/com/slack/lint/Foo.java:8: Warning: This may require a localized count modifier. If so, use LocalizationUtils.getFormattedCount(). Consult #plz-localization if you are unsure. [ArgInFormattedQuantityStringRes]\\n\" +\n          \"    String s = res.getQuantityString(0, 3, getFormattedCount(), 3);\\n\" +\n          \"                                                                ~\\n\" +\n          \"0 errors, 1 warnings\\n\"\n      )\n  }\n\n  @Test\n  fun getQuantityStringWithLocalizedFormatTestKotlin() {\n    lint()\n      .files(\n        kotlin(\n          \"\" +\n            \"package com.slack.lint\\n\" +\n            \"\\n\" +\n            \"import android.content.res.Resources\\n\" +\n            \"import com.slack.lint.Foo.LocalizationUtils.Companion.getFormattedCount\\n\" +\n            \"\\n\" +\n            \"class Foo {\\n\" +\n            \"  fun bar(res: Resources) {\\n\" +\n            \"    val s = res.getQuantityString(0, 3, getFormattedCount(), 3)\\n\" +\n            \"  }\\n\" +\n            \"\\n\" +\n            \"  class LocalizationUtils {\\n\" +\n            \"    companion object {\\n\" +\n            \"      fun getFormattedCount(): String {\\n\" +\n            \"        return \\\"\\\"\\n\" +\n            \"      }\\n\" +\n            \"    }\\n\" +\n            \"  }\\n\" +\n            \"}\\n\"\n        )\n      )\n      .run()\n      .expect(\n        \"src/com/slack/lint/Foo.kt:8: Warning: This may require a localized count modifier. If so, use LocalizationUtils.getFormattedCount(). Consult #plz-localization if you are unsure. [ArgInFormattedQuantityStringRes]\\n\" +\n          \"    val s = res.getQuantityString(0, 3, getFormattedCount(), 3)\\n\" +\n          \"                                                             ~\\n\" +\n          \"0 errors, 1 warnings\\n\"\n      )\n  }\n\n  @Test\n  fun getQuantityStringWithOutsideAssignmentLocalizedFormatTest() {\n    lint()\n      .files(\n        java(\n          (\"\" +\n            \"package com.slack.lint;\\n\" +\n            \"import static com.slack.lint.Foo.LocalizationUtils.getFormattedCount;\\n\" +\n            \"\\n\" +\n            \"import android.content.res.Resources;\\n\" +\n            \"\\n\" +\n            \"public class Foo {\\n\" +\n            \"  public void bar(Resources res) {\\n\" +\n            \"    final String a = getFormattedCount();\\n\" +\n            \"    String s = res.getQuantityString(0, 3, a, 3);\\n\" +\n            \"  }\\n\" +\n            \"  public static class LocalizationUtils {\\n\" +\n            \"   public static String getFormattedCount() { return \\\"\\\"; }\\n\" +\n            \"  }\\n\" +\n            \"}\\n\")\n        )\n      )\n      .run()\n      .expect(\n        \"src/com/slack/lint/Foo.java:9: Warning: This may require a localized count modifier. If so, use LocalizationUtils.getFormattedCount(). Consult #plz-localization if you are unsure. [ArgInFormattedQuantityStringRes]\\n\" +\n          \"    String s = res.getQuantityString(0, 3, a, 3);\\n\" +\n          \"                                              ~\\n\" +\n          \"0 errors, 1 warnings\\n\"\n      )\n  }\n\n  @Test\n  fun getQuantityStringWithOutsideAssignmentLocalizedFormatTestKotlin() {\n    lint()\n      .files(\n        kotlin(\n          \"\" +\n            \"package com.slack.lint\\n\" +\n            \"\\n\" +\n            \"import android.content.res.Resources\\n\" +\n            \"import com.slack.lint.Foo.LocalizationUtils.Companion.getFormattedCount\\n\" +\n            \"\\n\" +\n            \"class Foo {\\n\" +\n            \"  fun bar(res: Resources) {\\n\" +\n            \"    val s = res.getQuantityString(0, 3, getFormattedCount(), \\\"asdf\\\")\\n\" +\n            \"  }\\n\" +\n            \"\\n\" +\n            \"  class LocalizationUtils {\\n\" +\n            \"    companion object {\\n\" +\n            \"      fun getFormattedCount(): String {\\n\" +\n            \"        return \\\"\\\"\\n\" +\n            \"      }\\n\" +\n            \"    }\\n\" +\n            \"  }\\n\" +\n            \"}\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:8: Warning: This may require a localized count modifier. If so, use LocalizationUtils.getFormattedCount(). Consult #plz-localization if you are unsure. [ArgInFormattedQuantityStringRes]\n              val s = res.getQuantityString(0, 3, getFormattedCount(), \"asdf\")\n                                                                       ~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun getQuantityStringWithFullyQualifiedMethodName() {\n    lint()\n      .files(\n        kotlin(\n          \"\" +\n            \"package com.slack.lint\\n\" +\n            \"\\n\" +\n            \"import android.content.res.Resources\\n\" +\n            \"\\n\" +\n            \"class Foo {\\n\" +\n            \"  fun bar(res: Resources) {\\n\" +\n            \"    val s = res.getQuantityString(0, 3, LocalizationUtils.getFormattedCount(), \\\"asdf\\\")\\n\" +\n            \"  }\\n\" +\n            \"  \\n\" +\n            \"  class LocalizationUtils {\\n\" +\n            \"    companion object {\\n\" +\n            \"      fun getFormattedCount(): String {\\n\" +\n            \"        return \\\"\\\"\\n\" +\n            \"      }\\n\" +\n            \"    }\\n\" +\n            \"  }\\n\" +\n            \"}\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:7: Warning: This may require a localized count modifier. If so, use LocalizationUtils.getFormattedCount(). Consult #plz-localization if you are unsure. [ArgInFormattedQuantityStringRes]\n              val s = res.getQuantityString(0, 3, LocalizationUtils.getFormattedCount(), \"asdf\")\n                                                                                         ~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun getQuantityStringNoFormatArgsTest() {\n    lint()\n      .files(\n        java(\n          \"\" +\n            \"package com.slack.lint;\\n\" +\n            \"\\n\" +\n            \"import android.content.res.Resources;\\n\" +\n            \"\\n\" +\n            \"public class Foo {\\n\" +\n            \"  public void bar(Resources res) {\\n\" +\n            \"    String s = res.getQuantityString(0, 3);\\n\" +\n            \"  }\\n\" +\n            \"}\\n\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun getQuantityStringNoFormatArgsTestKotlin() {\n    lint()\n      .files(\n        kotlin(\n          \"\" +\n            \"package com.slack.lint\\n\" +\n            \"\\n\" +\n            \"import android.content.res.Resources\\n\" +\n            \"\\n\" +\n            \"class Foo {\\n\" +\n            \"  fun bar(res: Resources) {\\n\" +\n            \"    val s = res.getQuantityString(0, 3)\\n\" +\n            \"  }\\n\" +\n            \"}\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/BaseSlackLintTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.infrastructure.LintDetectorTest\nimport com.android.tools.lint.checks.infrastructure.TestFile\nimport com.android.tools.lint.checks.infrastructure.TestLintTask\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.utils.SdkUtils\nimport java.io.BufferedInputStream\nimport java.io.ByteArrayInputStream\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileNotFoundException\nimport java.io.InputStream\nimport java.net.MalformedURLException\nimport org.junit.runner.RunWith\nimport org.junit.runners.JUnit4\n\n@RunWith(JUnit4::class)\nabstract class BaseSlackLintTest : LintDetectorTest() {\n  private val rootPath = \"resources/test/com/slack/lint/data/\"\n  private val stubsPath = \"testStubs\"\n\n  /**\n   * Lint periodically adds new \"TestModes\" to LintDetectorTest. These modes act as a sort of chaos\n   * testing mechanism, adding different common variations of code (extra spaces, extra parens, etc)\n   * to the test files to ensure that lints are robust against them. They also make it quite\n   * difficult to test against and need extra work sometimes to properly support, so we expose this\n   * property to allow tests to skip certain modes that need more work in subsequent PRs.\n   */\n  open val skipTestModes: Array<TestMode>? = null\n\n  fun loadStub(stubName: String): TestFile {\n    return copy(stubsPath + File.separatorChar + stubName, \"src/main/java/$stubName\")\n  }\n\n  abstract override fun getDetector(): Detector\n\n  abstract override fun getIssues(): List<Issue>\n\n  override fun lint(): TestLintTask {\n    val sdkLocation = System.getProperty(\"android.sdk\") ?: System.getenv(\"ANDROID_HOME\")\n    val lintTask = super.lint()\n    sdkLocation?.let { lintTask.sdkHome(File(it)) }\n    lintTask.allowCompilationErrors(false)\n\n    skipTestModes?.let { testModesToSkip -> lintTask.skipTestModes(*testModesToSkip) }\n    return lintTask\n  }\n\n  /**\n   * The default finder for resources doesn't work with our file structure; this ensures it will.\n   *\n   * https://www.bignerdranch.com/blog/building-custom-lint-checks-in-android/\n   */\n  override fun getTestResource(relativePath: String, expectExists: Boolean): InputStream {\n    val path = (rootPath + relativePath).replace('/', File.separatorChar)\n    val file = File(getTestDataRootDir(), path)\n    if (file.exists()) {\n      try {\n        return BufferedInputStream(FileInputStream(file))\n      } catch (_: FileNotFoundException) {\n        if (expectExists) {\n          fail(\"Could not find file $relativePath\")\n        }\n      }\n    }\n    return BufferedInputStream(ByteArrayInputStream(\"\".toByteArray()))\n  }\n\n  private fun getTestDataRootDir(): File? {\n    val source = javaClass.protectionDomain.codeSource\n    if (source != null) {\n      val location = source.location\n      try {\n        val classesDir = SdkUtils.urlToFile(location)\n        return classesDir.parentFile!!.absoluteFile.parentFile!!.parentFile\n      } catch (e: MalformedURLException) {\n        fail(e.localizedMessage)\n      }\n    }\n    return null\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/CircuitScreenDataClassDetectorTest.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport org.junit.Test\n\nclass CircuitScreenDataClassDetectorTest : BaseSlackLintTest() {\n\n  override fun getDetector(): Detector = CircuitScreenDataClassDetector()\n\n  override fun getIssues(): List<Issue> = listOf(CircuitScreenDataClassDetector.ISSUE)\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - regular class implements Screen - fails`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              @Parcelize\n              class HomeScreen(val userId: String) : Screen {\n                  data class State(message: String) : State\n              }\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint()\n      .files(circuitScreenStub, testFile)\n      .run()\n      .expect(\n        \"\"\"\n        src/com/example/screens/HomeScreen.kt:6: Error: ${CircuitScreenDataClassDetector.MESSAGE} [${CircuitScreenDataClassDetector.ISSUE_ID}]\n        class HomeScreen(val userId: String) : Screen {\n        ~~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - data class implements Screen - succeeds`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              data class HomeScreen(val userId: String) : Screen\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint().files(circuitScreenStub, testFile).run().expectClean()\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - data object implements Screen - succeeds`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              data object SettingsScreen : Screen\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint().files(circuitScreenStub, testFile).run().expectClean()\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - interface extends Screen - succeeds`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              interface CustomScreen : Screen {\n                val id: String\n              }\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint().files(circuitScreenStub, testFile).run().expectClean()\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - regular class not implementing Screen - succeeds`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              class HomeViewModel(val userId: String)\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint().files(testFile).run().expectClean()\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - class name contains 'class' keyword - only replaces declaration`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              class MyClassScreen(val id: String) : Screen\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint()\n      .files(circuitScreenStub, testFile)\n      .run()\n      .expect(\n        \"\"\"\n        src/com/example/screens/MyClassScreen.kt:5: Error: ${CircuitScreenDataClassDetector.MESSAGE} [${CircuitScreenDataClassDetector.ISSUE_ID}]\n        class MyClassScreen(val id: String) : Screen\n        ~~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - regular object class implements Screen - suggests data object`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              object SettingsScreen : Screen\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint()\n      .files(circuitScreenStub, testFile)\n      .run()\n      .expect(\n        \"\"\"\n        src/com/example/screens/SettingsScreen.kt:5: Error: ${CircuitScreenDataClassDetector.MESSAGE} [${CircuitScreenDataClassDetector.ISSUE_ID}]\n        object SettingsScreen : Screen\n        ~~~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - open class implements Screen - succeeds`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              open class ProfileScreen(val userId: String) : Screen\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint().files(circuitScreenStub, testFile).run().expectClean()\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - companion object implements Screen - fails`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              class SomeClass {\n                  companion object NavScreen : Screen\n              }\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint().files(circuitScreenStub, testFile).run().expectClean()\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - nested class implements Screen - fails`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              class OuterClass {\n                  class NestedScreen(val id: String) : Screen\n              }\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint()\n      .files(circuitScreenStub, testFile)\n      .run()\n      .expect(\n        \"\"\"\n        src/com/example/screens/OuterClass.kt:6: Error: ${CircuitScreenDataClassDetector.MESSAGE} [${CircuitScreenDataClassDetector.ISSUE_ID}]\n            class NestedScreen(val id: String) : Screen\n            ~~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - abstract class implements Screen - succeeds`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              abstract class BaseScreen : Screen {\n                  abstract val screenId: String\n              }\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint().files(circuitScreenStub, testFile).run().expectClean()\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - sealed class implements Screen - fails`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              sealed class NavigationScreen : Screen {\n                  data class Home(val userId: String) : NavigationScreen()\n                  data object Settings : NavigationScreen()\n              }\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint().files(circuitScreenStub, testFile).run().expectClean()\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - inner class implements Screen - succeeds`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              class OuterClass {\n                  class InnerScreen(val id: String) : Screen\n              }\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint()\n      .files(circuitScreenStub, testFile)\n      .run()\n      .expect(\n        \"src/com/example/screens/OuterClass.kt:6: Error: Circuit Screen implementations should be data classes or data objects, not regular classes. [CircuitScreenShouldBeDataClass]\\n\" +\n          \"    class InnerScreen(val id: String) : Screen\\n\" +\n          \"    ~~~~~\\n\" +\n          \"1 error\"\n      )\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - class with no constructor parameters - suggests data object`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              class EmptyScreen : Screen\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint()\n      .files(circuitScreenStub, testFile)\n      .run()\n      .expect(\n        \"\"\"\n        src/com/example/screens/EmptyScreen.kt:5: Error: ${CircuitScreenDataClassDetector.MESSAGE} [${CircuitScreenDataClassDetector.ISSUE_ID}]\n        class EmptyScreen : Screen\n        ~~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - class with empty constructor - suggests data object`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              class EmptyScreen() : Screen\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint()\n      .files(circuitScreenStub, testFile)\n      .run()\n      .expect(\n        \"\"\"\n        src/com/example/screens/EmptyScreen.kt:5: Error: ${CircuitScreenDataClassDetector.MESSAGE} [${CircuitScreenDataClassDetector.ISSUE_ID}]\n        class EmptyScreen() : Screen\n        ~~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Test CircuitScreenDataClassDetector - local class implements Screen - succeeds`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package com.example.screens\n\n              import com.slack.circuit.runtime.screen.Screen\n\n              fun createScreen() {\n                  class LocalScreen : Screen\n                  return LocalScreen()\n              }\n          \"\"\"\n            .trimIndent()\n        )\n        .indented()\n\n    lint().files(circuitScreenStub, testFile).run().expectClean()\n  }\n\n  private val circuitScreenStub =\n    kotlin(\n        \"\"\"\n            package com.slack.circuit.runtime.screen\n\n            import android.os.Parcelable\n\n            interface Screen : Parcelable\n        \"\"\"\n          .trimIndent()\n      )\n      .indented()\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/DaggerIssuesDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport org.junit.Test\n\nclass DaggerIssuesDetectorTest : BaseSlackLintTest() {\n\n  private companion object {\n    private val javaxInjectStubs =\n      kotlin(\n          \"\"\"\n        package javax.inject\n\n        annotation class Inject\n        annotation class Qualifier\n      \"\"\"\n        )\n        .indented()\n\n    private val daggerStubs =\n      kotlin(\n          \"\"\"\n        package dagger\n\n        annotation class Binds\n        annotation class Provides\n        annotation class Module\n      \"\"\"\n        )\n        .indented()\n  }\n\n  override val skipTestModes: Array<TestMode> =\n    arrayOf(\n      TestMode.WHITESPACE,\n      TestMode.SUPPRESSIBLE,\n      // This mode adds new parameters to binds() functions, resulting in different error messages\n      TestMode.JVM_OVERLOADS,\n    )\n\n  override fun getDetector() = DaggerIssuesDetector()\n\n  override fun getIssues() = DaggerIssuesDetector.ISSUES.toList()\n\n  @Test\n  fun `bindings cannot be extension functions`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        daggerStubs,\n        kotlin(\n            \"\"\"\n                  package foo\n                  import javax.inject.Qualifier\n                  import dagger.Binds\n                  import dagger.Provides\n                  import dagger.Module\n\n                  @Qualifier\n                  annotation class MyQualifier\n\n                  @Module\n                  interface MyModule {\n                    @Binds fun Int.bind(): Number\n                    @Binds fun Long.bind(): Number\n                    @Binds fun Double.bind(): Number\n                    @Binds fun Float.bind(): Number\n                    @Binds fun Short.bind(): Number\n                    @Binds fun Byte.bind(): Number\n                    @Binds fun Char.bind(): Comparable<Char>\n                    @Binds fun String.bind(): Comparable<String>\n                    @Binds fun @receiver:MyQualifier Boolean.bind(): Comparable<Boolean>\n                  }\n\n                  @Module\n                  interface MyModule2 {\n                    @Provides fun Int.bind(): Number = this@bind\n                    @Provides fun Long.bind(): Number = this@bind\n                    @Provides fun Double.bind(): Number = this@bind\n                    @Provides fun Float.bind(): Number = this@bind\n                    @Provides fun Short.bind(): Number = this@bind\n                    @Provides fun Byte.bind(): Number = this@bind\n                    @Provides fun Char.bind(): Comparable<Char> = this@bind\n                    @Provides fun String.bind(): Comparable<String> = this@bind\n                    @Provides fun @receiver:MyQualifier Boolean.bind(): Comparable<Boolean> = this@bind\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/MyQualifier.kt:12: Error: @Binds/@Provides functions cannot be extensions [BindingReceiverParameter]\n          @Binds fun Int.bind(): Number\n                     ~~~\n        src/foo/MyQualifier.kt:13: Error: @Binds/@Provides functions cannot be extensions [BindingReceiverParameter]\n          @Binds fun Long.bind(): Number\n                     ~~~~\n        src/foo/MyQualifier.kt:14: Error: @Binds/@Provides functions cannot be extensions [BindingReceiverParameter]\n          @Binds fun Double.bind(): Number\n                     ~~~~~~\n        src/foo/MyQualifier.kt:15: Error: @Binds/@Provides functions cannot be extensions [BindingReceiverParameter]\n          @Binds fun Float.bind(): Number\n                     ~~~~~\n        src/foo/MyQualifier.kt:16: Error: @Binds/@Provides functions cannot be extensions [BindingReceiverParameter]\n          @Binds fun Short.bind(): Number\n                     ~~~~~\n        src/foo/MyQualifier.kt:17: Error: @Binds/@Provides functions cannot be extensions [BindingReceiverParameter]\n          @Binds fun Byte.bind(): Number\n                     ~~~~\n        src/foo/MyQualifier.kt:18: Error: @Binds/@Provides functions cannot be extensions [BindingReceiverParameter]\n          @Binds fun Char.bind(): Comparable<Char>\n                     ~~~~\n        src/foo/MyQualifier.kt:19: Error: @Binds/@Provides functions cannot be extensions [BindingReceiverParameter]\n          @Binds fun String.bind(): Comparable<String>\n                     ~~~~~~\n        src/foo/MyQualifier.kt:20: Error: @Binds/@Provides functions cannot be extensions [BindingReceiverParameter]\n          @Binds fun @receiver:MyQualifier Boolean.bind(): Comparable<Boolean>\n                                           ~~~~~~~\n        src/foo/MyQualifier.kt:25: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun Int.bind(): Number = this@bind\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:26: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun Long.bind(): Number = this@bind\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:27: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun Double.bind(): Number = this@bind\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:28: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun Float.bind(): Number = this@bind\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:29: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun Short.bind(): Number = this@bind\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:30: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun Byte.bind(): Number = this@bind\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:31: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun Char.bind(): Comparable<Char> = this@bind\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:32: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun String.bind(): Comparable<String> = this@bind\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:33: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun @receiver:MyQualifier Boolean.bind(): Comparable<Boolean> = this@bind\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        18 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `binds type mismatches`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        daggerStubs,\n        kotlin(\n            \"src/foo/TestModule.kt\",\n            \"\"\"\n                  package foo\n                  import javax.inject.Qualifier\n                  import dagger.Binds\n                  import dagger.Module\n\n                  sealed interface ItemDetail {\n                    object DetailTypeA : ItemDetail\n                  }\n\n                  interface ItemMapper<T : ItemDetail>\n\n                  class DetailTypeAItemMapper : ItemMapper<ItemDetail.DetailTypeA>\n\n                  @Module\n                  interface MyModule {\n                    @Binds fun validBind(real: Int): Number\n                    @Binds fun validBind(real: Boolean): Comparable<Boolean>\n                    @Binds fun invalidBind(real: Long): String\n                    @Binds fun invalidBind(real: Long): Comparable<Boolean>\n\n                    @Binds fun validComplexBinding(real: DetailTypeAItemMapper): ItemMapper<out ItemDetail>\n                    @Binds fun validComplexBinding2(real: DetailTypeAItemMapper): ItemMapper<*>\n                    @Binds fun invalidComplexBinding(real: DetailTypeAItemMapper): ItemMapper<ItemDetail>\n                  }\n                \"\"\",\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/TestModule.kt:18: Error: @Binds parameter/return must be type-assignable [BindsTypeMismatch]\n          @Binds fun invalidBind(real: Long): String\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/TestModule.kt:19: Error: @Binds parameter/return must be type-assignable [BindsTypeMismatch]\n          @Binds fun invalidBind(real: Long): Comparable<Boolean>\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/TestModule.kt:23: Error: @Binds parameter/return must be type-assignable [BindsTypeMismatch]\n          @Binds fun invalidComplexBinding(real: DetailTypeAItemMapper): ItemMapper<ItemDetail>\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `redundant types`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        daggerStubs,\n        kotlin(\n            \"\"\"\n                  package foo\n                  import javax.inject.Qualifier\n                  import dagger.Binds\n                  import dagger.Module\n\n                  @Qualifier\n                  annotation class MyQualifier\n\n                  @Module\n                  interface MyModule {\n                    @MyQualifier @Binds fun validBind(real: Boolean): Boolean\n                    @Binds fun validBind(@MyQualifier real: Boolean): Boolean\n                    @Binds fun invalidBind(real: Long): Long\n                    @Binds fun invalidBind(real: Long): Long\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/MyQualifier.kt:13: Error: @Binds functions should return a different type [RedundantBinds]\n          @Binds fun invalidBind(real: Long): Long\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:14: Error: @Binds functions should return a different type [RedundantBinds]\n          @Binds fun invalidBind(real: Long): Long\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `invalid return types`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        daggerStubs,\n        kotlin(\n            \"\"\"\n                  package foo\n                  import javax.inject.Qualifier\n                  import dagger.Binds\n                  import dagger.Provides\n                  import dagger.Module\n\n                  @Qualifier\n                  annotation class MyQualifier\n\n                  @Module\n                  abstract class MyModule {\n                    @Binds fun invalidBind1(@MyQualifier real: Unit)\n                    @Binds fun invalidBind2(@MyQualifier real: Unit): Unit\n                    @Provides fun invalidBind3() {\n\n                    }\n                    @Provides fun invalidBind4(): Unit {\n                     return Unit\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/MyQualifier.kt:14: Error: @Binds/@Provides must have a return type [BindingReturnType]\n          @Provides fun invalidBind3() {\n          ^\n        src/foo/MyQualifier.kt:17: Error: @Binds/@Provides must have a return type [BindingReturnType]\n          @Provides fun invalidBind4(): Unit {\n          ^\n        src/foo/MyQualifier.kt:12: Error: @Binds functions must be abstract [BindsMustBeAbstract]\n          @Binds fun invalidBind1(@MyQualifier real: Unit)\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyQualifier.kt:13: Error: @Binds functions must be abstract [BindsMustBeAbstract]\n          @Binds fun invalidBind2(@MyQualifier real: Unit): Unit\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `binds param counts`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        daggerStubs,\n        kotlin(\n            \"\"\"\n                  package foo\n                  import dagger.Binds\n                  import dagger.Module\n\n                  @Module\n                  interface MyModule {\n                    @Binds fun validBind(real: Int): Number\n                    @Binds fun invalidBind(real: Int, second: Int): Number\n                    @Binds fun invalidBind(): Number\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/MyModule.kt:8: Error: @Binds must have one parameter [BindsWrongParameterCount]\n          @Binds fun invalidBind(real: Int, second: Int): Number\n                                ~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:9: Error: @Binds must have one parameter [BindsWrongParameterCount]\n          @Binds fun invalidBind(): Number\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `binds must be abstract`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        daggerStubs,\n        kotlin(\n            \"\"\"\n                  package foo\n                  import dagger.Binds\n                  import dagger.Module\n\n                  @Module\n                  interface MyModule {\n                    @Binds fun validBind(real: Int): Number\n                    @Binds fun invalidBind(real: Int): Number { return real }\n                    @Binds fun invalidBind(real: Int): Number = real\n                  }\n\n                  @Module\n                  abstract class MyModule2 {\n                    @Binds abstract fun validBind(real: Int): Number\n                    @Binds fun invalidBind(real: Int): Number { return real }\n                    @Binds fun invalidBind(real: Int): Number = real\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/MyModule.kt:8: Error: @Binds functions must be abstract [BindsMustBeAbstract]\n          @Binds fun invalidBind(real: Int): Number { return real }\n                                                    ~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:9: Error: @Binds functions must be abstract [BindsMustBeAbstract]\n          @Binds fun invalidBind(real: Int): Number = real\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:15: Error: @Binds functions must be abstract [BindsMustBeAbstract]\n          @Binds fun invalidBind(real: Int): Number { return real }\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:16: Error: @Binds functions must be abstract [BindsMustBeAbstract]\n          @Binds fun invalidBind(real: Int): Number = real\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `provides cannot be abstract`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        daggerStubs,\n        kotlin(\n            \"\"\"\n                  package foo\n                  import dagger.Provides\n                  import dagger.Module\n\n                  @Module\n                  interface MyModule {\n                    @Provides fun invalidBind(real: Int): Number\n                    @Provides fun invalidBind(real: Int): Number { return real }\n                    @Provides fun invalidBind(real: Int): Number = real\n                  }\n\n                  @Module\n                  abstract class MyModule2 {\n                    @Provides abstract fun invalidProvides(real: Int): Number\n                    @Provides fun validBind(real: Int): Number { return real }\n                    @Provides fun validBind(real: Int): Number = real\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/MyModule.kt:7: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun invalidBind(real: Int): Number\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:8: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun invalidBind(real: Int): Number { return real }\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:9: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides fun invalidBind(real: Int): Number = real\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:14: Error: @Provides functions cannot be abstract [ProvidesMustNotBeAbstract]\n          @Provides abstract fun invalidProvides(real: Int): Number\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `must be in a module`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        daggerStubs,\n        kotlin(\n            \"\"\"\n                  package foo\n                  import dagger.Binds\n                  import dagger.Provides\n                  import dagger.Module\n\n                  interface MyModule {\n                    @Binds fun invalidBind(real: Int): Number\n\n                    companion object {\n                      @Provides fun invalidBind(): Int = 3\n                    }\n                  }\n\n                  abstract class MyModule2 {\n                    @Binds abstract fun invalidBind(real: Int): Number\n\n                    companion object {\n                      @Provides fun invalidBind(): Int = 3\n                    }\n                  }\n\n                  @Module\n                  interface MyModule3 {\n                    @Binds fun validBind(real: Int): Number\n\n                    companion object {\n                      @Provides fun validBind(): Int = 3\n                    }\n                  }\n\n                  @Module\n                  abstract class MyModule4 {\n                    @Binds abstract fun validBind(real: Int): Number\n\n                    companion object {\n                      @Provides fun validBind(): Int = 3\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/MyModule.kt:7: Error: @Binds/@Provides functions must be in modules [MustBeInModule]\n          @Binds fun invalidBind(real: Int): Number\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:10: Error: @Binds/@Provides functions must be in modules [MustBeInModule]\n            @Provides fun invalidBind(): Int = 3\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:15: Error: @Binds/@Provides functions must be in modules [MustBeInModule]\n          @Binds abstract fun invalidBind(real: Int): Number\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/MyModule.kt:18: Error: @Binds/@Provides functions must be in modules [MustBeInModule]\n            @Provides fun invalidBind(): Int = 3\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/DeprecatedAnnotationDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.detector.api.Detector\nimport org.junit.Test\n\nclass DeprecatedAnnotationDetectorTest : BaseSlackLintTest() {\n  override fun getDetector(): Detector = DeprecatedAnnotationDetector()\n\n  override fun getIssues() = listOf(DeprecatedAnnotationDetector.ISSUE_DEPRECATED_CALL)\n\n  @Test\n  fun `non-deprecated class has no warnings`() {\n    lint()\n      .files(\n        NON_DEPRECATED_CLASS,\n        java(\n            \"\"\"\n                  package slack.test;\n\n                  import slack.test.ThisIsNotDeprecated;\n\n                  public class TestClass {\n\n                    public void doStuff() {\n                      new ThisIsNotDeprecated();\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(DeprecatedAnnotationDetector.ISSUE_DEPRECATED_CALL)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `deprecated class has a warning`() {\n    lint()\n      .files(\n        DEPRECATED_CLASS,\n        java(\n            \"\"\"\n                  package slack.test;\n\n                  import slack.test.ThisIsDeprecated;\n\n                  public class TestClass {\n\n                    public void doStuff() {\n                      new ThisIsDeprecated();\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(DeprecatedAnnotationDetector.ISSUE_DEPRECATED_CALL)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/test/TestClass.java:8: Warning: This class or method is deprecated; consider using an alternative. [DeprecatedCall]\n              new ThisIsDeprecated();\n              ~~~~~~~~~~~~~~~~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `non-deprecated method use no warnings`() {\n    lint()\n      .files(\n        DEPRECATED_METHOD,\n        java(\n            \"\"\"\n                  package slack.test;\n\n                  import slack.test.ThisIsNotDeprecated;\n\n                  public class TestClass {\n\n                    public void doStuff() {\n                      new ThisIsNotDeprecated().thisIsNotDeprecated();\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(DeprecatedAnnotationDetector.ISSUE_DEPRECATED_CALL)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `deprecated method use has a warning`() {\n    lint()\n      .files(\n        DEPRECATED_METHOD,\n        java(\n            \"\"\"\n                  package slack.test;\n\n                  import slack.test.ThisIsNotDeprecated;\n\n                  public class TestClass {\n\n                    public void doStuff() {\n                      new ThisIsNotDeprecated().thisIsDeprecated();\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(DeprecatedAnnotationDetector.ISSUE_DEPRECATED_CALL)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/test/TestClass.java:8: Warning: slack.test.ThisIsNotDeprecated.thisIsDeprecated is deprecated; consider using an alternative. [DeprecatedCall]\n              new ThisIsNotDeprecated().thisIsDeprecated();\n              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `deprecated method use has a warning in kotlin`() {\n    lint()\n      .files(\n        DEPRECATED_METHOD,\n        kotlin(\n            \"\"\"\n                  package slack.test\n\n                  import slack.test.ThisIsNotDeprecated\n\n                  class TestClass {\n\n                    public fun doStuff() {\n                      ThisIsNotDeprecated().thisIsDeprecated()\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(DeprecatedAnnotationDetector.ISSUE_DEPRECATED_CALL)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/test/TestClass.kt:8: Warning: slack.test.ThisIsNotDeprecated.thisIsDeprecated is deprecated; consider using an alternative. [DeprecatedCall]\n              ThisIsNotDeprecated().thisIsDeprecated()\n              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `kotlin-sourced deprecated class use has a warning in kotlin`() {\n    lint()\n      .files(\n        DEPRECATED_CLASS_KOTLIN,\n        kotlin(\n            \"\"\"\n                  package slack.test\n\n                  import slack.test.ThisIsDeprecated\n\n                  class TestClass {\n\n                    public fun doStuff() {\n                      ThisIsDeprecated()\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(DeprecatedAnnotationDetector.ISSUE_DEPRECATED_CALL)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/test/TestClass.kt:8: Warning: slack.test.ThisIsDeprecated.ThisIsDeprecated is deprecated; consider using an alternative. [DeprecatedCall]\n              ThisIsDeprecated()\n              ~~~~~~~~~~~~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `kotlin-sourced deprecated method use has a warning in kotlin`() {\n    lint()\n      .files(\n        DEPRECATED_METHOD_KOTLIN,\n        kotlin(\n            \"\"\"\n                  package slack.test\n\n                  import slack.test.ThisIsNotDeprecated\n\n                  class TestClass {\n\n                    public fun doStuff() {\n                      ThisIsNotDeprecated().thisIsDeprecated()\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(DeprecatedAnnotationDetector.ISSUE_DEPRECATED_CALL)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/test/TestClass.kt:8: Warning: slack.test.ThisIsNotDeprecated.thisIsDeprecated is deprecated; consider using an alternative. [DeprecatedCall]\n              ThisIsNotDeprecated().thisIsDeprecated()\n              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  private val DEPRECATED_CLASS =\n    java(\n      \"\"\"\n        package slack.test;\n\n        @Deprecated()\n        class ThisIsDeprecated {\n\n        }\n        \"\"\"\n    )\n\n  private val NON_DEPRECATED_CLASS =\n    java(\n      \"\"\"\n        package slack.test;\n\n        import java.lang.Deprecated;\n\n        class ThisIsNotDeprecated {\n\n        }\n        \"\"\"\n    )\n\n  private val DEPRECATED_METHOD =\n    java(\n      \"\"\"\n        package slack.test;\n\n        import java.lang.Deprecated;\n\n        class ThisIsNotDeprecated {\n          @Deprecated()\n          public void thisIsDeprecated() {}\n\n          public void thisIsNotDeprecated() {}\n        }\n        \"\"\"\n    )\n\n  private val DEPRECATED_CLASS_KOTLIN =\n    kotlin(\n      \"\"\"\n          package slack.test\n\n          import kotlin.Deprecated\n\n          @Deprecated\n          class ThisIsDeprecated {\n\n          }\n        \"\"\"\n    )\n\n  private val DEPRECATED_METHOD_KOTLIN =\n    kotlin(\n      \"\"\"\n          package slack.test\n\n          import kotlin.Deprecated\n\n          class ThisIsNotDeprecated {\n\n            @Deprecated\n            public fun thisIsDeprecated() {\n            }\n\n            public fun thisIsNotDeprecated() {}\n          }\n        \"\"\"\n    )\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/DeprecatedSqlUsageDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport org.junit.Test\n\n@Suppress(\"UnstableApiUsage\")\nclass DeprecatedSqlUsageDetectorTest : BaseSlackLintTest() {\n\n  override fun getDetector(): Detector = DeprecatedSqlUsageDetector()\n\n  override fun getIssues(): MutableList<Issue> = mutableListOf(DeprecatedSqlUsageDetector.ISSUE)\n\n  @Test\n  fun testJavaInspection() {\n    val deprecatedJavaExample =\n      java(\n          \"\"\"\n          package foo;\n\n          import android.database.sqlite.SQLiteDatabase;\n\n          public static class SqlUsageTestFailure {\n            public static void delete(SQLiteDatabase db) {\n              db.execSQL(\"DROP TABLE IF EXISTS foo\");\n            }\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(deprecatedJavaExample)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/SqlUsageTestFailure.java:7: Warning: All SQL querying should be performed using SqlDelight [DeprecatedSqlUsage]\n              db.execSQL(\"DROP TABLE IF EXISTS foo\");\n              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun testKotlinInspection() {\n    val deprecatedKotlinExample =\n      kotlin(\n          \"\"\"\n          package foo\n\n          import android.database.sqlite.SQLiteDatabase\n\n          object SqlUsageTestFailure {\n            fun delete(db: SQLiteDatabase) {\n              db.execSQL(\"DROP TABLE IF EXISTS foo\")\n            }\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(deprecatedKotlinExample)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/SqlUsageTestFailure.kt:7: Warning: All SQL querying should be performed using SqlDelight [DeprecatedSqlUsage]\n              db.execSQL(\"DROP TABLE IF EXISTS foo\")\n              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun testInnocentJava() {\n    val innocentJavaExample =\n      java(\n          \"\"\"\n          package foo;\n\n          import android.database.sqlite.SQLiteDatabase;\n\n          public static class SqlUsageTestFailure {\n            public static void delete(SQLiteDatabase db) {\n              db.getVersion();\n            }\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(innocentJavaExample).run().expectClean()\n  }\n\n  @Test\n  fun testInnocentKotlin() {\n    val innocentKotlinExample =\n      kotlin(\n          \"\"\"\n          package foo\n\n          object SqlUsageTestFailure {\n            fun delete(db: SupportSQLiteDatabase) {\n              db.getVersion()\n              FeatureFlagStore().update(\"foo\")\n            }\n          }\n\n          class FeatureFlagStore {\n            fun update(feature: String) = Unit\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(innocentKotlinExample).run().expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/DoNotCallProvidersDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\n@file:Suppress(\"UnstableApiUsage\")\n\npackage slack.lint\n\nimport org.junit.Ignore\nimport org.junit.Test\n\nclass DoNotCallProvidersDetectorTest : BaseSlackLintTest() {\n\n  private companion object {\n    private val javaxAnnotation =\n      kotlin(\n          \"\"\"\n        package javax.annotation\n\n        annotation class Generated(val message: String)\n      \"\"\"\n        )\n        .indented()\n    private val daggerStubs =\n      kotlin(\n          \"\"\"\n        package dagger\n\n        annotation class Binds\n        annotation class Provides\n        annotation class Module\n      \"\"\"\n        )\n        .indented()\n\n    private val daggerProducerStubs =\n      kotlin(\n          \"\"\"\n        package dagger.producers\n\n        annotation class Produces\n      \"\"\"\n        )\n        .indented()\n  }\n\n  override fun getDetector() = DoNotCallProvidersDetector()\n\n  override fun getIssues() = listOf(DoNotCallProvidersDetector.ISSUE)\n\n  @Ignore(\n    \"This fails on github actions for some reason, but we're upstreaming this to Dagger anyway\"\n  )\n  @Test\n  fun kotlin() {\n    lint()\n      .files(\n        javaxAnnotation,\n        daggerStubs,\n        daggerProducerStubs,\n        kotlin(\n            \"\"\"\n                  package foo\n                  import dagger.Binds\n                  import dagger.Module\n                  import dagger.Provides\n                  import dagger.producers.Produces\n                  import javax.annotation.Generated\n\n                  @Module\n                  abstract class MyModule {\n\n                    @Binds fun binds1(input: String): Comparable<String>\n                    @Binds fun String.binds2(): Comparable<String>\n\n                    fun badCode() {\n                      binds1(\"this is bad\")\n                      \"this is bad\".binds2()\n                      provider()\n                      producer()\n                    }\n\n                    companion object {\n                      @Provides\n                      fun provider(): String {\n                        return \"\"\n                      }\n                      @Produces\n                      fun producer(): String {\n                        return \"\"\n                      }\n                    }\n                  }\n\n                  @Generated(\"Totes generated code\")\n                  abstract class GeneratedCode {\n                    fun doStuff() {\n                      moduleInstance().binds1(\"this is technically fine but would never happen in dagger\")\n                      MyModule.provider()\n                      MyModule.producer()\n                    }\n\n                    abstract fun moduleInstance(): MyModule\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n              src/foo/MyModule.kt:15: Error: Dagger provider methods should not be called directly by user code. [DoNotCallProviders]\n                  binds1(\"this is bad\")\n                  ~~~~~~~~~~~~~~~~~~~~~\n              src/foo/MyModule.kt:16: Error: Dagger provider methods should not be called directly by user code. [DoNotCallProviders]\n                  \"this is bad\".binds2()\n                   ~~~~~~~~~~~~~~~~~~~~~\n              src/foo/MyModule.kt:17: Error: Dagger provider methods should not be called directly by user code. [DoNotCallProviders]\n                  provider()\n                  ~~~~~~~~~~\n              src/foo/MyModule.kt:18: Error: Dagger provider methods should not be called directly by user code. [DoNotCallProviders]\n                  producer()\n                  ~~~~~~~~~~\n              4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java() {\n    lint()\n      .files(\n        javaxAnnotation,\n        daggerStubs,\n        daggerProducerStubs,\n        java(\n            \"\"\"\n                  package foo;\n                  import dagger.Binds;\n                  import dagger.Module;\n                  import dagger.Provides;\n                  import dagger.producers.Produces;\n                  import javax.annotation.Generated;\n\n                  class Holder {\n                    @Module\n                    abstract class MyModule {\n\n                      @Binds Comparable<String> binds1(String input);\n\n                      void badCode() {\n                        binds1(\"this is bad\");\n                        provider();\n                        producer();\n                      }\n\n                      @Provides\n                      static String provider() {\n                        return \"\";\n                      }\n                      @Produces\n                      static String producer() {\n                        return \"\";\n                      }\n                    }\n\n                    @Generated(\"Totes generated code\")\n                    abstract class GeneratedCode {\n                      void doStuff() {\n                        moduleInstance().binds1(\"this is technically fine but would never happen in dagger\");\n                        MyModule.provider();\n                        MyModule.producer();\n                      }\n\n                      abstract MyModule moduleInstance();\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n              src/foo/Holder.java:15: Error: Dagger provider methods should not be called directly by user code. [DoNotCallProviders]\n                    binds1(\"this is bad\");\n                    ~~~~~~~~~~~~~~~~~~~~~\n              src/foo/Holder.java:16: Error: Dagger provider methods should not be called directly by user code. [DoNotCallProviders]\n                    provider();\n                    ~~~~~~~~~~\n              src/foo/Holder.java:17: Error: Dagger provider methods should not be called directly by user code. [DoNotCallProviders]\n                    producer();\n                    ~~~~~~~~~~\n              3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/ExceptionMessageDetectorTest.kt",
    "content": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage slack.lint\n\nimport org.junit.Test\n\nclass ExceptionMessageDetectorTest : BaseSlackLintTest() {\n  override fun getDetector() = ExceptionMessageDetector()\n\n  override fun getIssues() = listOf(ExceptionMessageDetector.ISSUE)\n\n  @Test\n  fun checkWithMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        check(true) {\"Message\"}\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkWithNamedParameterValue() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        check(value = true) {\"Message\"}\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkWithNamedParameterMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        check(true, lazyMessage = {\"Message\"})\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkWithNamedParameterValueAndMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        check(value = true, lazyMessage = {\"Message\"})\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkWithNamedParameterValueAndMessageReversed() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        check(lazyMessage = {\"Message\"}, value = true)\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkWithoutMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        check(true)\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n                    src/test/test.kt:5: Error: Please specify a lazyMessage param for check [ExceptionMessage]\n                                            check(true)\n                                            ~~~~~\n                    1 errors, 0 warnings\n                \"\"\"\n      )\n  }\n\n  @Test\n  fun checkFromDifferentPackageWithoutMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        check(true)\n                    }\n\n                    fun check(boolean: Boolean) {}\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkNotNullWithMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        checkNotNull(null) {\"Message\"}\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkNotNullWithNamedParameterValue() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        checkNotNull(value = null) {\"Message\"}\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkNotNullWithNamedParameterMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        checkNotNull(null, lazyMessage = {\"Message\"})\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkNotNullWithNamedParameterValueAndMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        checkNotNull(value = null, lazyMessage = {\"Message\"})\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkNotNullWithNamedParameterValueAndMessageReversed() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        checkNotNull(lazyMessage = {\"Message\"}, value = null)\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun checkNotNullWithoutMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        checkNotNull(null)\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n                    src/test/test.kt:5: Error: Please specify a lazyMessage param for checkNotNull [ExceptionMessage]\n                                            checkNotNull(null)\n                                            ~~~~~~~~~~~~\n                    1 errors, 0 warnings\n                \"\"\"\n      )\n  }\n\n  @Test\n  fun checkNotNullFromDifferentPackageWithoutMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        checkNotNull(null)\n                    }\n\n                    fun checkNotNull(value: Any?) {}\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireWithMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        require(true) {\"Message\"}\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireWithNamedParameterValue() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        require(value = true) {\"Message\"}\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireWithNamedParameterMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        require(true, lazyMessage = {\"Message\"})\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireWithNamedParameterValueAndMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        require(value = true, lazyMessage = {\"Message\"})\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireWithNamedParameterValueAndMessageReversed() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        require(lazyMessage = {\"Message\"}, value = true)\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireWithoutMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        require(true)\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n                    src/test/test.kt:5: Error: Please specify a lazyMessage param for require [ExceptionMessage]\n                                            require(true)\n                                            ~~~~~~~\n                    1 errors, 0 warnings\n                \"\"\"\n      )\n  }\n\n  @Test\n  fun requireFromDifferentPackageWithoutMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        require(true)\n                    }\n\n                    fun require(boolean: Boolean) {}\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireNotNullWithMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        requireNotNull(null) {\"Message\"}\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireNotNullWithNamedParameterValue() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        requireNotNull(value = null) {\"Message\"}\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireNotNullWithNamedParameterMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        requireNotNull(null, lazyMessage = {\"Message\"})\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireNotNullWithNamedParameterValueAndMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        requireNotNull(value = null, lazyMessage = {\"Message\"})\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireNotNullWithNamedParameterValueAndMessageReversed() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        requireNotNull(lazyMessage = {\"Message\"}, value = null)\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requireNotNullWithoutMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        requireNotNull(null)\n                    }\n                    \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n                    src/test/test.kt:5: Error: Please specify a lazyMessage param for requireNotNull [ExceptionMessage]\n                                            requireNotNull(null)\n                                            ~~~~~~~~~~~~~~\n                    1 errors, 0 warnings\n                \"\"\"\n      )\n  }\n\n  @Test\n  fun requireNotNullFromDifferentPackageWithoutMessage() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n                    package test\n\n                    fun content() {\n                        requireNotNull(null)\n                    }\n\n                    fun requireNotNull(value: Any?) {}\n                    \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/FragmentDaggerFieldInjectionDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\n@file:Suppress(\"UnstableApiUsage\")\n\npackage slack.lint\n\nimport org.junit.Test\n\nclass FragmentDaggerFieldInjectionDetectorTest : BaseSlackLintTest() {\n\n  private val javaxInjectStubs =\n    kotlin(\n        \"\"\"\n        package javax.inject\n\n        annotation class Inject\n      \"\"\"\n      )\n      .indented()\n\n  private val assistedInjectStubs =\n    kotlin(\n        \"\"\"\n      package dagger.assisted\n\n      annotation class AssistedInject\n      \"\"\"\n      )\n      .indented()\n\n  private val topLevelFragment =\n    java(\n        \"\"\"\n      package androidx.fragment.app;\n\n      public abstract class Fragment {\n      }\n    \"\"\"\n      )\n      .indented()\n\n  private val coreUiAbstractFragment =\n    kotlin(\n        \"\"\"\n      package slack.coreui.fragment\n\n      import androidx.fragment.app.Fragment\n\n      abstract class ViewBindingFragment: Fragment() {\n\n      }\n    \"\"\"\n      )\n      .indented()\n\n  override fun getDetector() = FragmentDaggerFieldInjectionDetector()\n\n  override fun getIssues() = FragmentDaggerFieldInjectionDetector.issues.toList()\n\n  @Test\n  fun `Kotlin - fragment has field injection warnings`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        assistedInjectStubs,\n        topLevelFragment,\n        coreUiAbstractFragment,\n        kotlin(\n            \"\"\"\n              package foo\n\n              import javax.inject.Inject\n              import slack.coreui.fragment.ViewBindingFragment\n\n              class MyFragment : ViewBindingFragment() {\n\n                private lateinit var notAnnotated: String\n                private val defaulted: String = \"defaulted\"\n\n                @Inject\n                private lateinit var stringValue1: String\n                @Inject\n                private lateinit var intValue1: Int\n\n                fun onCreate() {\n                  notAnnotated = \"fast\"\n                }\n              }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/MyFragment.kt:11: Error: Fragment dependencies should be injected using the Fragment's constructor. [FragmentFieldInjection]\n            @Inject\n            ^\n          src/foo/MyFragment.kt:13: Error: Fragment dependencies should be injected using the Fragment's constructor. [FragmentFieldInjection]\n            @Inject\n            ^\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Java - fragment has field injection warnings`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        assistedInjectStubs,\n        topLevelFragment,\n        coreUiAbstractFragment,\n        java(\n            \"\"\"\n              package foo;\n\n              import javax.inject.Inject;\n              import slack.coreui.fragment.ViewBindingFragment;\n\n              public class MyFragment extends ViewBindingFragment {\n\n                private static String notAnnotated;\n                private final String defaulted = \"defaulted\";\n\n                @Inject\n                String stringValue1;\n                @Inject\n                Int intValue1;\n\n                public void onCreate() {\n                  notAnnotated = \"fast\";\n                }\n              }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/MyFragment.java:11: Error: Fragment dependencies should be injected using the Fragment's constructor. [FragmentFieldInjection]\n            @Inject\n            ^\n          src/foo/MyFragment.java:13: Error: Fragment dependencies should be injected using the Fragment's constructor. [FragmentFieldInjection]\n            @Inject\n            ^\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Kotlin - fragment has field injection errors when constructor injection exists`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        assistedInjectStubs,\n        topLevelFragment,\n        coreUiAbstractFragment,\n        kotlin(\n            \"\"\"\n              package foo\n\n              import javax.inject.Inject\n              import dagger.assisted.AssistedInject\n              import slack.coreui.fragment.ViewBindingFragment\n\n              class MyFragment @Inject constructor(\n                private val flag: Boolean\n              ): ViewBindingFragment() {\n\n                private lateinit var notAnnotated: String\n                private val defaulted: String = \"defaulted\"\n\n                @Inject\n                private lateinit var stringValue1: String\n                @Inject\n                private lateinit var intValue1: Int\n\n                fun onCreate() {\n                  notAnnotated = \"fast\"\n                }\n              }\n\n              class MyFragmentAssistedInject @AssistedInject constructor(\n                private val flag: Boolean\n              ): ViewBindingFragment() {\n\n                private lateinit var notAnnotated: String\n                private val defaulted: String = \"defaulted\"\n\n                @Inject\n                private lateinit var stringValue1: String\n                @Inject\n                private lateinit var intValue1: Int\n\n                fun onCreate() {\n                  notAnnotated = \"fast\"\n                }\n              }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/MyFragment.kt:14: Error: Fragment dependencies should be injected using constructor injections only. [FragmentConstructorInjection]\n            @Inject\n            ^\n          src/foo/MyFragment.kt:16: Error: Fragment dependencies should be injected using constructor injections only. [FragmentConstructorInjection]\n            @Inject\n            ^\n          src/foo/MyFragment.kt:31: Error: Fragment dependencies should be injected using constructor injections only. [FragmentConstructorInjection]\n            @Inject\n            ^\n          src/foo/MyFragment.kt:33: Error: Fragment dependencies should be injected using constructor injections only. [FragmentConstructorInjection]\n            @Inject\n            ^\n          4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Java - fragment has field injection errors when constructor injection with basic Inject exists`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        assistedInjectStubs,\n        topLevelFragment,\n        coreUiAbstractFragment,\n        java(\n            \"\"\"\n              package foo;\n\n              import javax.inject.Inject;\n              import slack.coreui.fragment.ViewBindingFragment;\n\n              public class MyFragment extends ViewBindingFragment {\n\n                private static String notAnnotated;\n                private final String defaulted = \"defaulted\";\n\n                @Inject\n                String stringValue1;\n                @Inject\n                Int intValue1;\n\n                @Inject\n                public MyFragment(Boolean flag) {\n\n                }\n\n                public void onCreate() {\n                  notAnnotated = \"fast\";\n                }\n              }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/MyFragment.java:11: Error: Fragment dependencies should be injected using constructor injections only. [FragmentConstructorInjection]\n            @Inject\n            ^\n          src/foo/MyFragment.java:13: Error: Fragment dependencies should be injected using constructor injections only. [FragmentConstructorInjection]\n            @Inject\n            ^\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Java - fragment has field injection errors when constructor injection with AssistedInject exists`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        assistedInjectStubs,\n        topLevelFragment,\n        coreUiAbstractFragment,\n        java(\n            \"\"\"\n              package foo;\n\n              import javax.inject.Inject;\n              import dagger.assisted.AssistedInject;\n              import slack.coreui.fragment.ViewBindingFragment;\n\n              public class MyFragment extends ViewBindingFragment {\n\n                private static String notAnnotated;\n                private final String defaulted = \"defaulted\";\n\n                @Inject\n                String stringValue1;\n                @Inject\n                Int intValue1;\n\n                @AssistedInject\n                public MyFragment(Boolean flag) {\n\n                }\n\n                public void onCreate() {\n                  notAnnotated = \"fast\";\n                }\n              }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/MyFragment.java:12: Error: Fragment dependencies should be injected using constructor injections only. [FragmentConstructorInjection]\n            @Inject\n            ^\n          src/foo/MyFragment.java:14: Error: Fragment dependencies should be injected using constructor injections only. [FragmentConstructorInjection]\n            @Inject\n            ^\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Kotlin - abstract fragment does not get warnings`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        assistedInjectStubs,\n        topLevelFragment,\n        coreUiAbstractFragment,\n        kotlin(\n            \"\"\"\n              package foo\n\n              import javax.inject.Inject\n              import androidx.fragment.app.Fragment\n\n              abstract class MyFragment : Fragment() {\n\n                private lateinit var notAnnotated: String\n                private val defaulted: String = \"defaulted\"\n\n                @Inject\n                private lateinit var stringValue1: String\n                @Inject\n                private lateinit var intValue1: Int\n\n                fun onCreate() {\n                  notAnnotated = \"fast\"\n                }\n              }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/MyFragment.kt:11: Error: Fragment dependencies should be injected using the Fragment's constructor. [FragmentFieldInjection]\n            @Inject\n            ^\n          src/foo/MyFragment.kt:13: Error: Fragment dependencies should be injected using the Fragment's constructor. [FragmentFieldInjection]\n            @Inject\n            ^\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Kotlin - non-fragment class also get warnings`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        kotlin(\n            \"\"\"\n            package foo\n\n            import javax.inject.Inject\n\n            class NonFragment {\n\n              private lateinit var notAnnotated: String\n              private val defaulted: String = \"defaulted\"\n\n              @Inject\n              private lateinit var stringValue1: String\n              @Inject\n              private lateinit var intValue1: Int\n\n              fun onCreate() {\n                notAnnotated = \"fast\"\n              }\n            }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `Java - abstract fragment does not get warnings`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        assistedInjectStubs,\n        topLevelFragment,\n        coreUiAbstractFragment,\n        java(\n            \"\"\"\n              package foo;\n\n              import javax.inject.Inject;\n              import dagger.assisted.AssistedInject;\n              import slack.coreui.fragment.ViewBindingFragment;\n\n              public abstract class MyFragment extends ViewBindingFragment {\n\n                private static String notAnnotated;\n                private final String defaulted = \"defaulted\";\n\n                @Inject\n                String stringValue1;\n                @Inject\n                Int intValue1;\n\n                public void onCreate() {\n                  notAnnotated = \"fast\";\n                }\n              }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/MyFragment.java:12: Error: Fragment dependencies should be injected using the Fragment's constructor. [FragmentFieldInjection]\n            @Inject\n            ^\n          src/foo/MyFragment.java:14: Error: Fragment dependencies should be injected using the Fragment's constructor. [FragmentFieldInjection]\n            @Inject\n            ^\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Java - non-fragment does not get warnings`() {\n    lint()\n      .files(\n        javaxInjectStubs,\n        assistedInjectStubs,\n        topLevelFragment,\n        coreUiAbstractFragment,\n        java(\n            \"\"\"\n              package foo;\n\n              import javax.inject.Inject;\n              import dagger.assisted.AssistedInject;\n\n              public class MyFragment {\n\n                private static String notAnnotated;\n                private final String defaulted = \"defaulted\";\n\n                @Inject\n                String stringValue1;\n                @Inject\n                Int intValue1;\n\n                public void onCreate() {\n                  notAnnotated = \"fast\";\n                }\n              }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/GuavaPreconditionsDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport com.android.tools.lint.detector.api.Detector\nimport org.junit.Test\n\nclass GuavaPreconditionsDetectorTest : BaseSlackLintTest() {\n\n  override val skipTestModes: Array<TestMode> = arrayOf(TestMode.WHITESPACE)\n\n  override fun getDetector(): Detector = GuavaPreconditionsDetector()\n\n  override fun getIssues() = GuavaPreconditionsDetector.issues.toList()\n\n  private val guavaPreconditionsStub =\n    java(\n        \"\"\"\n      package com.google.common.base;\n\n      public final class Preconditions {\n          public static void checkState(boolean expression) {}\n          public static void checkArgument(boolean expression) {}\n          public static <T extends Object> T checkNotNull(T reference) {}\n          public static int checkElementIndex(int index, int size) { return 0; }\n      }\n    \"\"\"\n      )\n      .indented()\n\n  private val slackPreconditionsStub =\n    kotlin(\n        \"\"\"\n          @file:JvmName(\"JavaPreconditions\")\n\n          package slack.commons\n          fun check(condition: Boolean) {}\n          fun require(condition: Boolean) {}\n          fun checkNotNull(condition: Boolean) {}\n          fun <T> checkNotNull(value: T): T {}\n        \"\"\"\n          .trimIndent()\n      )\n      .indented()\n\n  @Test\n  fun `Java - Using Guava Preconditions with static reference will show warnings`() {\n    lint()\n      .files(\n        guavaPreconditionsStub,\n        java(\n            \"\"\"\n            package foo;\n\n            import com.google.common.base.Preconditions;\n\n            public class Foo {\n\n              boolean isTrue = Preconditions.checkState(1 == 1);\n\n              void act() {\n                Preconditions.checkState(1 == 1);\n                Preconditions.checkArgument(1 == 1);\n                Preconditions.checkNotNull(\"Hello\");\n                Preconditions.checkElementIndex(0, 1);\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*GuavaPreconditionsDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/Foo.java:7: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n            boolean isTrue = Preconditions.checkState(1 == 1);\n                                           ~~~~~~~~~~\n          src/foo/Foo.java:10: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              Preconditions.checkState(1 == 1);\n                            ~~~~~~~~~~\n          src/foo/Foo.java:11: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              Preconditions.checkArgument(1 == 1);\n                            ~~~~~~~~~~~~~\n          src/foo/Foo.java:12: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              Preconditions.checkNotNull(\"Hello\");\n                            ~~~~~~~~~~~~\n          src/foo/Foo.java:13: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              Preconditions.checkElementIndex(0, 1);\n                            ~~~~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/foo/Foo.java line 7: Use Slack's JavaPreconditions checks:\n          @@ -7 +7\n          -   boolean isTrue = Preconditions.checkState(1 == 1);\n          +   boolean isTrue = slack.commons.JavaPreconditions.checkState(1 == 1);\n          Fix for src/foo/Foo.java line 10: Use Slack's JavaPreconditions checks:\n          @@ -10 +10\n          -     Preconditions.checkState(1 == 1);\n          +     slack.commons.JavaPreconditions.checkState(1 == 1);\n          Fix for src/foo/Foo.java line 11: Use Slack's JavaPreconditions checks:\n          @@ -11 +11\n          -     Preconditions.checkArgument(1 == 1);\n          +     slack.commons.JavaPreconditions.checkArgument(1 == 1);\n          Fix for src/foo/Foo.java line 12: Use Slack's JavaPreconditions checks:\n          @@ -12 +12\n          -     Preconditions.checkNotNull(\"Hello\");\n          +     slack.commons.JavaPreconditions.checkNotNull(\"Hello\");\n          Fix for src/foo/Foo.java line 13: Use Slack's JavaPreconditions checks:\n          @@ -13 +13\n          -     Preconditions.checkElementIndex(0, 1);\n          +     slack.commons.JavaPreconditions.checkElementIndex(0, 1);\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Java - Using Guava Preconditions with fully qualfied references will show warnings`() {\n    lint()\n      .files(\n        guavaPreconditionsStub,\n        java(\n            \"\"\"\n            package foo;\n\n            public class Foo {\n\n              boolean isTrue = com.google.common.base.Preconditions.checkState(1 == 1);\n\n              void act() {\n                com.google.common.base.Preconditions.checkState(1 == 1);\n                com.google.common.base.Preconditions.checkArgument(1 == 1);\n                com.google.common.base.Preconditions.checkNotNull(\"Hello\");\n                com.google.common.base.Preconditions.checkElementIndex(0, 1);\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*GuavaPreconditionsDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/Foo.java:5: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n            boolean isTrue = com.google.common.base.Preconditions.checkState(1 == 1);\n                                                                  ~~~~~~~~~~\n          src/foo/Foo.java:8: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              com.google.common.base.Preconditions.checkState(1 == 1);\n                                                   ~~~~~~~~~~\n          src/foo/Foo.java:9: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              com.google.common.base.Preconditions.checkArgument(1 == 1);\n                                                   ~~~~~~~~~~~~~\n          src/foo/Foo.java:10: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              com.google.common.base.Preconditions.checkNotNull(\"Hello\");\n                                                   ~~~~~~~~~~~~\n          src/foo/Foo.java:11: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              com.google.common.base.Preconditions.checkElementIndex(0, 1);\n                                                   ~~~~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/foo/Foo.java line 5: Use Slack's JavaPreconditions checks:\n          @@ -5 +5\n          -   boolean isTrue = com.google.common.base.Preconditions.checkState(1 == 1);\n          +   boolean isTrue = slack.commons.JavaPreconditions.checkState(1 == 1);\n          Fix for src/foo/Foo.java line 8: Use Slack's JavaPreconditions checks:\n          @@ -8 +8\n          -     com.google.common.base.Preconditions.checkState(1 == 1);\n          +     slack.commons.JavaPreconditions.checkState(1 == 1);\n          Fix for src/foo/Foo.java line 9: Use Slack's JavaPreconditions checks:\n          @@ -9 +9\n          -     com.google.common.base.Preconditions.checkArgument(1 == 1);\n          +     slack.commons.JavaPreconditions.checkArgument(1 == 1);\n          Fix for src/foo/Foo.java line 10: Use Slack's JavaPreconditions checks:\n          @@ -10 +10\n          -     com.google.common.base.Preconditions.checkNotNull(\"Hello\");\n          +     slack.commons.JavaPreconditions.checkNotNull(\"Hello\");\n          Fix for src/foo/Foo.java line 11: Use Slack's JavaPreconditions checks:\n          @@ -11 +11\n          -     com.google.common.base.Preconditions.checkElementIndex(0, 1);\n          +     slack.commons.JavaPreconditions.checkElementIndex(0, 1);\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Java - Using Guava Preconditions with static imports will show warnings`() {\n    lint()\n      .files(\n        guavaPreconditionsStub,\n        java(\n            \"\"\"\n            package foo;\n\n            import static com.google.common.base.Preconditions.checkState;\n            import static com.google.common.base.Preconditions.checkArgument;\n            import static com.google.common.base.Preconditions.checkNotNull;\n            import static com.google.common.base.Preconditions.checkElementIndex;\n\n            public class Foo {\n\n              private boolean isTrue = checkState(1 == 1);\n\n              void act() {\n                checkState(1 == 1);\n                checkArgument(1 == 1);\n                checkNotNull(\"Hello\");\n                checkElementIndex(0, 1);\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*GuavaPreconditionsDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/Foo.java:10: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n            private boolean isTrue = checkState(1 == 1);\n                                     ~~~~~~~~~~\n          src/foo/Foo.java:13: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              checkState(1 == 1);\n              ~~~~~~~~~~\n          src/foo/Foo.java:14: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              checkArgument(1 == 1);\n              ~~~~~~~~~~~~~\n          src/foo/Foo.java:15: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              checkNotNull(\"Hello\");\n              ~~~~~~~~~~~~\n          src/foo/Foo.java:16: Error: Use Slack's JavaPreconditions instead of Guava's Preconditions checks [GuavaChecksUsed]\n              checkElementIndex(0, 1);\n              ~~~~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/foo/Foo.java line 10: Use Slack's JavaPreconditions checks:\n          @@ -10 +10\n          -   private boolean isTrue = checkState(1 == 1);\n          +   private boolean isTrue = slack.commons.JavaPreconditions.checkState(1 == 1);\n          Fix for src/foo/Foo.java line 13: Use Slack's JavaPreconditions checks:\n          @@ -13 +13\n          -     checkState(1 == 1);\n          +     slack.commons.JavaPreconditions.checkState(1 == 1);\n          Fix for src/foo/Foo.java line 14: Use Slack's JavaPreconditions checks:\n          @@ -14 +14\n          -     checkArgument(1 == 1);\n          +     slack.commons.JavaPreconditions.checkArgument(1 == 1);\n          Fix for src/foo/Foo.java line 15: Use Slack's JavaPreconditions checks:\n          @@ -15 +15\n          -     checkNotNull(\"Hello\");\n          +     slack.commons.JavaPreconditions.checkNotNull(\"Hello\");\n          Fix for src/foo/Foo.java line 16: Use Slack's JavaPreconditions checks:\n          @@ -16 +16\n          -     checkElementIndex(0, 1);\n          +     slack.commons.JavaPreconditions.checkElementIndex(0, 1);\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Java - Using Slack Preconditions with static reference will be clean`() {\n    lint()\n      .files(\n        slackPreconditionsStub,\n        java(\n            \"\"\"\n            package foo;\n\n            import slack.commons.JavaPreconditions;\n\n            public class Foo {\n\n              boolean isTrue = JavaPreconditions.check(1 == 1);\n\n              void act() {\n                JavaPreconditions.check(1 == 1);\n                JavaPreconditions.require(1 == 1);\n                JavaPreconditions.checkNotNull(\"Hello\");\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*GuavaPreconditionsDetector.issues.toTypedArray())\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `Java - Using Slack Preconditions with static imports will be clean`() {\n    lint()\n      .files(\n        slackPreconditionsStub,\n        java(\n            \"\"\"\n            package foo;\n\n            import static slack.commons.JavaPreconditions.check;\n            import static slack.commons.JavaPreconditions.checkNotNull;\n            import static slack.commons.JavaPreconditions.require;\n\n            public class Foo {\n\n              private boolean isTrue = check(1 == 1);\n\n              void act() {\n                check(1 == 1);\n                checkNotNull(\"Hello\");\n                require(true);\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*GuavaPreconditionsDetector.issues.toTypedArray())\n      .allowCompilationErrors() // Until AGP 7.1.0\n      // https://groups.google.com/g/lint-dev/c/BigCO8sMhKU\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `Kotlin - Using Guava Preconditions with static reference will show warnings`() {\n    lint()\n      .files(\n        guavaPreconditionsStub,\n        kotlin(\n            \"\"\"\n            package foo\n\n            import com.google.common.base.Preconditions\n\n            class Foo {\n\n              val isTrue = Preconditions.checkState(false)\n\n              fun act() {\n                Preconditions.checkState(true)\n                Preconditions.checkArgument(false)\n                Preconditions.checkNotNull(\"Hello\")\n                Preconditions.checkElementIndex(0, 1)\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*GuavaPreconditionsDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/Foo.kt:7: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n            val isTrue = Preconditions.checkState(false)\n                                       ~~~~~~~~~~\n          src/foo/Foo.kt:10: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              Preconditions.checkState(true)\n                            ~~~~~~~~~~\n          src/foo/Foo.kt:11: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              Preconditions.checkArgument(false)\n                            ~~~~~~~~~~~~~\n          src/foo/Foo.kt:12: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              Preconditions.checkNotNull(\"Hello\")\n                            ~~~~~~~~~~~~\n          src/foo/Foo.kt:13: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              Preconditions.checkElementIndex(0, 1)\n                            ~~~~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/foo/Foo.kt line 7: Use Kotlin's standard library checks:\n          @@ -7 +7\n          -   val isTrue = Preconditions.checkState(false)\n          +   val isTrue = check(false)\n          Fix for src/foo/Foo.kt line 10: Use Kotlin's standard library checks:\n          @@ -10 +10\n          -     Preconditions.checkState(true)\n          +     check(true)\n          Fix for src/foo/Foo.kt line 11: Use Kotlin's standard library checks:\n          @@ -11 +11\n          -     Preconditions.checkArgument(false)\n          +     require(false)\n          Fix for src/foo/Foo.kt line 12: Use Kotlin's standard library checks:\n          @@ -12 +12\n          -     Preconditions.checkNotNull(\"Hello\")\n          +     checkNotNull(\"Hello\")\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Kotlin - Using Guava Preconditions with fully qualified references will show warnings`() {\n    lint()\n      .files(\n        guavaPreconditionsStub,\n        kotlin(\n            \"\"\"\n            package foo\n\n            class Foo {\n\n              val isTrue = com.google.common.base.Preconditions.checkState(false)\n\n              fun act() {\n                com.google.common.base.Preconditions.checkState(true)\n                com.google.common.base.Preconditions.checkArgument(false)\n                com.google.common.base.Preconditions.checkNotNull(\"Hello\")\n                com.google.common.base.Preconditions.checkElementIndex(0, 1)\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*GuavaPreconditionsDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/Foo.kt:5: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n            val isTrue = com.google.common.base.Preconditions.checkState(false)\n                                                              ~~~~~~~~~~\n          src/foo/Foo.kt:8: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              com.google.common.base.Preconditions.checkState(true)\n                                                   ~~~~~~~~~~\n          src/foo/Foo.kt:9: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              com.google.common.base.Preconditions.checkArgument(false)\n                                                   ~~~~~~~~~~~~~\n          src/foo/Foo.kt:10: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              com.google.common.base.Preconditions.checkNotNull(\"Hello\")\n                                                   ~~~~~~~~~~~~\n          src/foo/Foo.kt:11: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              com.google.common.base.Preconditions.checkElementIndex(0, 1)\n                                                   ~~~~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/foo/Foo.kt line 5: Use Kotlin's standard library checks:\n          @@ -5 +5\n          -   val isTrue = com.google.common.base.Preconditions.checkState(false)\n          +   val isTrue = check(false)\n          Fix for src/foo/Foo.kt line 8: Use Kotlin's standard library checks:\n          @@ -8 +8\n          -     com.google.common.base.Preconditions.checkState(true)\n          +     check(true)\n          Fix for src/foo/Foo.kt line 9: Use Kotlin's standard library checks:\n          @@ -9 +9\n          -     com.google.common.base.Preconditions.checkArgument(false)\n          +     require(false)\n          Fix for src/foo/Foo.kt line 10: Use Kotlin's standard library checks:\n          @@ -10 +10\n          -     com.google.common.base.Preconditions.checkNotNull(\"Hello\")\n          +     checkNotNull(\"Hello\")\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `Kotlin - Using Guava Preconditions with static imports will show warnings`() {\n    lint()\n      .files(\n        guavaPreconditionsStub,\n        kotlin(\n            \"\"\"\n            package foo\n\n            import com.google.common.base.Preconditions.checkState\n            import com.google.common.base.Preconditions.checkArgument\n            import com.google.common.base.Preconditions.checkNotNull\n            import com.google.common.base.Preconditions.checkElementIndex\n\n            class Foo {\n\n              val isTrue = checkState(false);\n\n              fun act() {\n                checkState(true)\n                checkArgument(false)\n                checkNotNull(\"Hello\")\n                checkElementIndex(0, 1)\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*GuavaPreconditionsDetector.issues.toTypedArray())\n      .skipTestModes(TestMode.IMPORT_ALIAS)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/Foo.kt:10: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n            val isTrue = checkState(false);\n                         ~~~~~~~~~~\n          src/foo/Foo.kt:13: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              checkState(true)\n              ~~~~~~~~~~\n          src/foo/Foo.kt:14: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              checkArgument(false)\n              ~~~~~~~~~~~~~\n          src/foo/Foo.kt:15: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              checkNotNull(\"Hello\")\n              ~~~~~~~~~~~~\n          src/foo/Foo.kt:16: Error: Kotlin precondition checks should use the Kotlin standard library checks [GuavaPreconditionsUsedInKotlin]\n              checkElementIndex(0, 1)\n              ~~~~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n    // Can't assert fix diffs because lint produces non-deterministic diffs per test mode\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/InjectInJavaDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport org.junit.Test\n\nclass InjectInJavaDetectorTest : BaseSlackLintTest() {\n\n  companion object {\n    private val JAVAX_STUBS =\n      kotlin(\n        \"\"\"\n        package javax.inject\n\n        annotation class Inject\n        \"\"\"\n          .trimIndent()\n      )\n    private val DAGGER_STUBS =\n      kotlin(\n        \"\"\"\n        package dagger\n\n        annotation class Module\n        \"\"\"\n          .trimIndent()\n      )\n    private val ASSISTED_STUBS =\n      kotlin(\n        \"\"\"\n        package dagger.assisted\n\n        annotation class AssistedInject\n        annotation class AssistedFactory\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  override fun getDetector() = InjectInJavaDetector()\n\n  override fun getIssues() = listOf(InjectInJavaDetector.ISSUE)\n\n  @Test\n  fun kotlinIsOk() {\n    lint()\n      .files(\n        JAVAX_STUBS,\n        DAGGER_STUBS,\n        ASSISTED_STUBS,\n        kotlin(\n            \"\"\"\n            package test.pkg\n\n            import javax.inject.Inject\n            import dagger.Module\n            import dagger.assisted.AssistedInject\n            import dagger.assisted.AssistedFactory\n\n            class KotlinClass @Inject constructor(val constructorInjected: String) {\n              @Inject lateinit var memberInjected: String\n\n              @Inject fun methodInject(value: String) {\n\n              }\n            }\n\n            class KotlinAssistedClass @AssistedInject constructor(\n              @Assisted val assistedParam: String\n            ) {\n              @AssistedFactory\n              interface Factory {\n                fun create(assistedParam: String): KotlinAssistedClass\n              }\n            }\n\n            @Module\n            object ExampleModule\n          \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun javaIsNotOk() {\n    lint()\n      .files(\n        JAVAX_STUBS,\n        DAGGER_STUBS,\n        ASSISTED_STUBS,\n        java(\n            \"\"\"\n            package test.pkg;\n\n            import javax.inject.Inject;\n            import dagger.Module;\n            import dagger.assisted.AssistedInject;\n            import dagger.assisted.AssistedFactory;\n\n            class JavaClass {\n              @Inject String memberInjected;\n\n              @Inject JavaClass(String constructorInjected) {\n\n              }\n\n              @Inject void methodInject(String value) {\n\n              }\n\n              static class JavaAssistedClass {\n\n                @AssistedInject JavaAssistedClass(@Assisted String assistedParam) {\n\n                }\n\n                @AssistedFactory\n                interface Factory {\n                  JavaAssistedClass create(String assistedParam);\n                }\n              }\n\n              @Module static abstract class ExampleModule {\n\n              }\n            }\n\n          \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/test/pkg/JavaClass.java:9: Error: Only Kotlin classes should be injected in order for Anvil to work. [InjectInJava]\n            @Inject String memberInjected;\n            ~~~~~~~\n          src/test/pkg/JavaClass.java:11: Error: Only Kotlin classes should be injected in order for Anvil to work. [InjectInJava]\n            @Inject JavaClass(String constructorInjected) {\n            ~~~~~~~\n          src/test/pkg/JavaClass.java:15: Error: Only Kotlin classes should be injected in order for Anvil to work. [InjectInJava]\n            @Inject void methodInject(String value) {\n            ~~~~~~~\n          src/test/pkg/JavaClass.java:21: Error: Only Kotlin classes should be injected in order for Anvil to work. [InjectInJava]\n              @AssistedInject JavaAssistedClass(@Assisted String assistedParam) {\n              ~~~~~~~~~~~~~~~\n          src/test/pkg/JavaClass.java:25: Error: Only Kotlin classes should be injected in order for Anvil to work. [InjectInJava]\n              @AssistedFactory\n              ~~~~~~~~~~~~~~~~\n          src/test/pkg/JavaClass.java:31: Error: Only Kotlin classes should be injected in order for Anvil to work. [InjectInJava]\n            @Module static abstract class ExampleModule {\n            ~~~~~~~\n          6 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/JavaOnlyDetectorTest.kt",
    "content": "// Copyright (C) 2020 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.infrastructure.TestFile\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport org.junit.Ignore\nimport org.junit.Test\n\nclass JavaOnlyDetectorTest : BaseSlackLintTest() {\n\n  companion object {\n    val ANNOTATIONS: TestFile =\n      kotlin(\n          \"\"\"\n          package slack.lint.annotations\n          annotation class KotlinOnly(val reason: String)\n          annotation class JavaOnly(val reason: String)\n          \"\"\"\n        )\n        .indented()\n  }\n\n  override fun getDetector() = JavaOnlyDetector()\n\n  override fun getIssues() = listOf(JavaOnlyDetector.ISSUE)\n\n  // TODO fix these\n  override val skipTestModes: Array<TestMode> = arrayOf(TestMode.SUPPRESSIBLE)\n\n  @Test\n  fun positive() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/Test.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  class Test {\n                    @JavaOnly fun g() {}\n                    @JavaOnly(\"satisfying explanation\") fun f() {}\n                    fun m() {\n                      g()\n                      f()\n                      val r = this::g\n                    }\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/test/pkg/Test.kt:7: Error: This method should not be called from Kotlin, see its documentation for details. [JavaOnlyDetector]\n              g()\n              ~~~\n          test/test/pkg/Test.kt:8: Error: This method should not be called from Kotlin: satisfying explanation [JavaOnlyDetector]\n              f()\n              ~~~\n          test/test/pkg/Test.kt:9: Error: This method should not be called from Kotlin, see its documentation for details. [JavaOnlyDetector]\n              val r = this::g\n                      ~~~~~~~\n          3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Ignore(\"Un-ignore when https://groups.google.com/forum/#!topic/lint-dev/8Nr0-SDdHbk is fixed\")\n  @Test\n  fun positivePackage() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        java(\n            \"test/test/pkg/package-info.java\",\n            \"\"\"\n                  @slack.lint.annotations.JavaOnly\n                  package test.pkg;\n                  import slack.lint.annotations.JavaOnly;\"\"\",\n          )\n          .indented(),\n        java(\n            \"test/test/pkg/A.java\",\n            \"\"\"\n                  package test.pkg;\n                  public class A {\n                    public static void f() {}\n                  }\n                \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg2/Test.kt\",\n            \"\"\"\n                  package test.pkg2\n                  import slack.lint.annotations.JavaOnly\n                  class Test {\n                    fun m() {\n                      test.pkg.A.f()\n                    }\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/test/pkg2/Test.kt:5: Error: This method should not be called from Kotlin: see its documentation for details. [JavaOnlyDetector]\n              f()\n              ~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun annotateBothFunction() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/Test.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  import slack.lint.annotations.KotlinOnly\n                  interface Test {\n                    @JavaOnly @KotlinOnly fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/test/pkg/Test.kt:5: Error: Cannot annotate functions with both @KotlinOnly and @JavaOnly [JavaOnlyDetector]\n            @JavaOnly @KotlinOnly fun f()\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun annotateBothClass() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/Test.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  import slack.lint.annotations.KotlinOnly\n                  @JavaOnly @KotlinOnly interface Test {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/test/pkg/Test.kt:4: Error: Cannot annotate types with both @KotlinOnly and @JavaOnly [JavaOnlyDetector]\n          @JavaOnly @KotlinOnly interface Test {\n          ^\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun requiredOverrideKotlin() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.KotlinOnly\n                  interface A {\n                    @KotlinOnly fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B : A {\n                    override fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/test/pkg/B.kt:3: Error: Function overrides f in A which is annotated @KotlinOnly, it should also be annotated. [JavaOnlyDetector]\n            override fun f()\n            ~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for test/test/pkg/B.kt line 3: Add @KotlinOnly:\n          @@ -3 +3\n          -   override fun f()\n          +   @slack.lint.annotations.KotlinOnly override fun f()\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun annotatedOverrideKotlin() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.KotlinOnly\n                  interface A {\n                    @KotlinOnly fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.KotlinOnly\n                  class B : A {\n                    @KotlinOnly override fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requiredOverrideJava() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  interface A {\n                    @JavaOnly fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B : A {\n                    override fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/test/pkg/B.kt:3: Error: Function overrides f in A which is annotated @JavaOnly, it should also be annotated. [JavaOnlyDetector]\n            override fun f()\n            ~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for test/test/pkg/B.kt line 3: Add @JavaOnly:\n          @@ -3 +3\n          -   override fun f()\n          +   @slack.lint.annotations.JavaOnly override fun f()\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun annotatedOverrideJava() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  interface A {\n                    @JavaOnly fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  class B : A {\n                    @JavaOnly override fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requiredOverrideKotlinClass() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.KotlinOnly\n                  @KotlinOnly interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B : A {\n                    override fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/test/pkg/B.kt:2: Error: Type subclasses/implements A in A.kt which is annotated @KotlinOnly, it should also be annotated. [JavaOnlyDetector]\n          class B : A {\n          ^\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for test/test/pkg/B.kt line 2: Add @KotlinOnly:\n          @@ -2 +2\n          - class B : A {\n          + @slack.lint.annotations.KotlinOnly class B : A {\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun annotatedOverrideKotlinClass() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.KotlinOnly\n                  @KotlinOnly interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.KotlinOnly\n                  @KotlinOnly class B : A {\n                    override fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun requiredOverrideJavaClass() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  @JavaOnly interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B : A {\n                    override fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/test/pkg/B.kt:2: Error: Type subclasses/implements A in A.kt which is annotated @JavaOnly, it should also be annotated. [JavaOnlyDetector]\n          class B : A {\n          ^\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for test/test/pkg/B.kt line 2: Add @JavaOnly:\n          @@ -2 +2\n          - class B : A {\n          + @slack.lint.annotations.JavaOnly class B : A {\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun annotatedOverrideJavaClass() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  @JavaOnly interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  @JavaOnly class B : A {\n                    override fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  // The interface tries to make Object#toString @JavaOnly, and because\n  // the declaration in B is implicit it doesn't get checked.\n  // In practice, making default Object methods @JavaOnly isn't super\n  // useful - typically users interface with the interface directly\n  // (e.g. Hasher) or there's an override that has unwanted behaviour (Localizable).\n  // NOTE: This has slightly different behavior than the error prone checker, as in UAST\n  // the overridden method actually propagates down through types and ErrorProne's doesn't.\n  @Test\n  fun interfaceRedeclaresObjectMethod() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/I.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  interface I {\n                    @JavaOnly override fun toString(): String\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B : I\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/Test.kt\",\n            \"\"\"\n                  package test.pkg\n                  class Test {\n                    fun f(b: B) {\n                      b.toString()\n                      val i: I = b\n                      i.toString()\n                    }\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/test/pkg/Test.kt:4: Error: This method should not be called from Kotlin, see its documentation for details. [JavaOnlyDetector]\n              b.toString()\n              ~~~~~~~~~~~~\n          test/test/pkg/Test.kt:6: Error: This method should not be called from Kotlin, see its documentation for details. [JavaOnlyDetector]\n              i.toString()\n              ~~~~~~~~~~~~\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun lambdaPositiveClass() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  @JavaOnly fun interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B {\n                    fun f(): A = {}\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n            test/test/pkg/B.kt:3: Error: Cannot create lambda instances of @JavaOnly-annotated type A (in A.kt) in Kotlin. Make a concrete class instead. [JavaOnlyDetector]\n              fun f(): A = {}\n                           ~~\n            1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun lambdaPositiveMethod() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  fun interface A {\n                    @JavaOnly fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B {\n                    fun f(): A = {}\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n            test/test/pkg/B.kt:3: Error: This method should not be expressed as a lambda in Kotlin, see its documentation for details. [JavaOnlyDetector]\n              fun f(): A = {}\n                           ~~\n            1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun lambdaNegative() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B {\n                    fun f(): A = {}\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun lambdaNegativeReturnFun() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  interface A {\n                    @JavaOnly fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  class B {\n                    @JavaOnly fun f(): A = {}\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun lambdaNegativeReturnClass() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  @JavaOnly interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  class B {\n                    @JavaOnly fun f(): A = {}\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun anonymousClassPositiveClass() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  @JavaOnly interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B {\n                    fun f(): A = object : A() {\n                      override fun f() {}\n                    }\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n            test/test/pkg/B.kt:3: Error: Cannot create anonymous instances of @JavaOnly-annotated type A (in A.kt) in Kotlin. Make a concrete class instead. [JavaOnlyDetector]\n              fun f(): A = object : A() {\n                           ^\n            1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun anonymousClassNegative() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  class B {\n                    fun f(): A = object : A() {\n                      override fun f() {}\n                    }\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun anonymousClassNegativeReturnMethod() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  interface A {\n                    @JavaOnly fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  class B {\n                    @JavaOnly fun f(): A = object : A() {\n                      @JavaOnly override fun f() {}\n                    }\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun anonymousClassNegativeReturnClass() {\n    lint()\n      .detector(JavaOnlyDetector())\n      .files(\n        ANNOTATIONS,\n        kotlin(\n            \"test/test/pkg/A.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  @JavaOnly interface A {\n                    fun f()\n                  }\n                  \"\"\",\n          )\n          .indented(),\n        kotlin(\n            \"test/test/pkg/B.kt\",\n            \"\"\"\n                  package test.pkg\n                  import slack.lint.annotations.JavaOnly\n                  class B {\n                    @JavaOnly fun f(): A = object : A() {\n                      override fun f() {}\n                    }\n                  }\n                  \"\"\",\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/JsonInflaterMoshiCompatibilityDetectorTest.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.infrastructure.LintDetectorTest\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.junit.runners.JUnit4\n\n@RunWith(JUnit4::class)\nclass JsonInflaterMoshiCompatibilityDetectorTest : LintDetectorTest() {\n\n  override fun getDetector(): Detector {\n    return JsonInflaterMoshiCompatibilityDetector()\n  }\n\n  override fun getIssues(): List<Issue> {\n    return listOf(JsonInflaterMoshiCompatibilityDetector.ISSUE)\n  }\n\n  // Stubs for required annotations and classes\n  private val jsonClassStub =\n    java(\n      \"\"\"\n    package com.squareup.moshi;\n\n    import java.lang.annotation.Retention;\n    import java.lang.annotation.RetentionPolicy;\n\n    @Retention(RetentionPolicy.RUNTIME)\n    public @interface JsonClass {\n        boolean generateAdapter();\n    }\n  \"\"\"\n    )\n\n  private val adaptedByStub =\n    kotlin(\n      \"\"\"\n    package dev.zacsweers.moshix.adapters\n\n    import java.lang.annotation.Retention\n    import java.lang.annotation.RetentionPolicy\n\n    @Retention(RetentionPolicy.RUNTIME)\n    public annotation class AdaptedBy(val adapter: KClass<*>, val nullSafe: Boolean = true)\n  \"\"\"\n    )\n\n  private val jsonInflaterStub =\n    kotlin(\n      \"\"\"\n    package slack.commons.json\n\n    import java.lang.reflect.Type\n\n    interface JsonInflater {\n      fun <T : Any> inflate(jsonData: String, typeOfT: Type): T\n\n      fun <T : Any> inflate(jsonData: String, clazz: Class<T>): T\n\n      fun <T : Any> deflate(value: T, clazz: Class<T>): String\n\n      fun deflate(value: Any, type: Type): String\n    }\n  \"\"\"\n    )\n\n  private val parameterizedTypeStub =\n    kotlin(\n      \"\"\"\n          package com.squareup.moshi\n\n          import java.lang.reflect.ParameterizedType\n          import java.lang.reflect.Type\n\n          class StubParameterizedType(\n              private val rawType: Type,\n              private val typeArguments: Array<Type>,\n              private val ownerType: Type? = null\n          ) : ParameterizedType {\n              override fun getActualTypeArguments(): Array<Type> = typeArguments\n              override fun getRawType(): Type = rawType\n              override fun getOwnerType(): Type? = ownerType\n          }\n      \"\"\"\n        .trimIndent()\n    )\n\n  private val typeLabelStub =\n    kotlin(\n        \"\"\"\n      package dev.zacsweers.moshix.sealed.annotations\n\n      annotation class TypeLabel(val label: String, val alternateLabels: Array<String> = [])\n    \"\"\"\n      )\n      .indented()\n\n  private val defaultObjectStub =\n    kotlin(\n        \"\"\"\n      package dev.zacsweers.moshix.sealed.annotations\n\n      annotation class DefaultObject\n    \"\"\"\n      )\n      .indented()\n\n  @Test\n  fun testDocumentationExample() {\n    testMissingJsonClassAnnotation()\n  }\n\n  @Test\n  fun testDataClassJsonClassTrue() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n\n        @JsonClass(generateAdapter = true)\n        data class ValidModel(\n            val id: String,\n            val name: String,\n            val count: Int\n        )\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", ValidModel::class.java)\n            val json = jsonInflater.deflate(model, ValidModel::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testDataClassJsonClassFalse() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n\n        @JsonClass(generateAdapter = false)\n        data class ValidModel(\n            val id: String,\n            val name: String,\n            val count: Int\n        )\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", ValidModel::class.java)\n            val json = jsonInflater.deflate(model)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      // We only check for the existence of @JsonClass in the detector as we get more granular in\n      // MoshiUsageDetector.\n      .expectClean()\n  }\n\n  @Test\n  fun testDataClassJsonClassEmpty() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n\n        @JsonClass()\n        data class ValidModel(\n            val id: String,\n            val name: String,\n            val count: Int\n        )\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", ValidModel::class.java)\n            val json = jsonInflater.deflate(model, ValidModel::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testDataClassAdaptedBy() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        adaptedByStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import dev.zacsweers.moshix.adapters.AdaptedBy\n        import slack.commons.json.JsonInflater\n\n        @AdaptedBy(String::class)\n        data class ValidModel(\n            val id: String,\n            val name: String,\n            val count: Int\n        )\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", ValidModel::class.java)\n            val json = jsonInflater.deflate(model, ValidModel::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testValidDataClassInList() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        adaptedByStub,\n        parameterizedTypeStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import com.squareup.moshi.StubParameterizedType\n        import slack.commons.json.JsonInflater\n\n        @JsonClass(generateAdapter = true)\n        data class ValidModel(\n            val id: String,\n            val name: String,\n            val count: Int\n        )\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val type = StubParameterizedType(\n                List::class.java,\n                arrayOf(ValidModel::class.java)\n            )\n            val model = jsonInflater.inflate<List<ValidModel>>(\"{}\", type)\n            val json = jsonInflater.deflate(model, type)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testValidMapOfPrimitives() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        adaptedByStub,\n        parameterizedTypeStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import com.squareup.moshi.StubParameterizedType\n        import slack.commons.json.JsonInflater\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val type = StubParameterizedType(\n                Map::class.java,\n                arrayOf(String::class.java, Int::class.java)\n            )\n            val model = jsonInflater.inflate<Map<String, Int>>(\"{}\", type)\n            val json = jsonInflater.deflate(model, type)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testInvalidDataClassInList() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        adaptedByStub,\n        parameterizedTypeStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import com.squareup.moshi.StubParameterizedType\n        import slack.commons.json.JsonInflater\n\n        data class InvalidModel(\n            val id: String,\n            val name: String,\n            val count: Int\n        )\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val type = StubParameterizedType(\n                List::class.java,\n                arrayOf(InvalidModel::class.java)\n            )\n            val model = jsonInflater.inflate<List<InvalidModel>>(\"{}\", type)\n            val json = jsonInflater.deflate(model, type)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expect(\n        \"\"\"\n                src/test/InvalidModel.kt:19: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                            val model = jsonInflater.inflate<List<InvalidModel>>(\"{}\", type)\n                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n                src/test/InvalidModel.kt:20: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                            val json = jsonInflater.deflate(model, type)\n                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n                2 errors, 0 warnings\n              \"\"\"\n      )\n  }\n\n  @Test\n  fun testMissingJsonClassAnnotation() {\n    lint()\n      .files(\n        jsonInflaterStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import slack.commons.json.JsonInflater\n\n        data class InvalidModel(\n            val id: String,\n            val name: String,\n            val count: Int\n        )\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", InvalidModel::class.java)\n            val json = jsonInflater.deflate(model, InvalidModel::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/InvalidModel.kt:13: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                    val model = jsonInflater.inflate(\"{}\", InvalidModel::class.java)\n                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/test/InvalidModel.kt:14: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                    val json = jsonInflater.deflate(model, InvalidModel::class.java)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        2 errors, 0 warnings\n      \"\"\"\n      )\n  }\n\n  @Test\n  fun testNonDataClass() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n\n        @JsonClass(generateAdapter = true)\n        class InvalidModel(\n            val id: String,\n            val name: String,\n            val count: Int\n        )\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", InvalidModel::class.java)\n            val json = jsonInflater.deflate(model, InvalidModel::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testAbstractClass() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n\n        @JsonClass(generateAdapter = true)\n        abstract class InvalidModel(\n            val id: String,\n            val name: String,\n            val count: Int\n        )\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", InvalidModel::class.java)\n            val json = jsonInflater.deflate(model, InvalidModel::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/InvalidModel.kt:15: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                    val model = jsonInflater.inflate(\"{}\", InvalidModel::class.java)\n                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/test/InvalidModel.kt:16: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                    val json = jsonInflater.deflate(model, InvalidModel::class.java)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        2 errors, 0 warnings\n      \"\"\"\n      )\n  }\n\n  @Test\n  fun testEnumClass() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n\n        @JsonClass(generateAdapter = false)\n        enum class ValidEnum {\n            UNKNOWN,\n            UP,\n            DOWN,\n            LEFT,\n            RIGHT\n        }\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", ValidEnum::class.java)\n            val json = jsonInflater.deflate(model, ValidEnum::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testEnumClassMissingJsonClassAnnotation() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n\n        enum class ValidEnum {\n            UNKNOWN,\n            UP,\n            DOWN,\n            LEFT,\n            RIGHT\n        }\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", ValidEnum::class.java)\n            val json = jsonInflater.deflate(model, ValidEnum::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expect(\n        \"\"\"\n            src/test/ValidEnum.kt:16: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                        val model = jsonInflater.inflate(\"{}\", ValidEnum::class.java)\n                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n            src/test/ValidEnum.kt:17: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                        val json = jsonInflater.deflate(model, ValidEnum::class.java)\n                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n            2 errors\n      \"\"\"\n      )\n  }\n\n  @Test\n  fun testNonSealedInterface() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        typeLabelStub,\n        defaultObjectStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n        import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n        import dev.zacsweers.moshix.sealed.annotations.DefaultObject\n\n        @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n        interface Animal {\n            @TypeLabel(\"dog\")\n            @JsonClass(generateAdapter = true)\n            data class Dog(val name: String) : Animal\n\n            @TypeLabel(\"cat\")\n            @JsonClass(generateAdapter = true)\n            data class Cat(val age: Int) : Animal\n\n            @DefaultObject\n            object Default : Animal\n        }\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", Animal::class.java)\n            val json = jsonInflater.deflate(model, Animal::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expect(\n        \"\"\"\n            src/test/Animal.kt:24: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                        val model = jsonInflater.inflate(\"{}\", Animal::class.java)\n                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n            src/test/Animal.kt:25: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                        val json = jsonInflater.deflate(model, Animal::class.java)\n                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n            2 errors\n      \"\"\"\n      )\n  }\n\n  @Test\n  fun testSealedInterface() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        typeLabelStub,\n        defaultObjectStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n        import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n        import dev.zacsweers.moshix.sealed.annotations.DefaultObject\n\n        @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n        sealed interface Animal {\n            @TypeLabel(\"dog\")\n            @JsonClass(generateAdapter = true)\n            data class Dog(val name: String) : Animal\n\n            @TypeLabel(\"cat\")\n            @JsonClass(generateAdapter = true)\n            data class Cat(val age: Int) : Animal\n\n            @DefaultObject\n            object Default : Animal\n        }\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", Animal::class.java)\n            val json = jsonInflater.deflate(model, Animal::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testSealedInterfaceMissingJsonClassAnnotation() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        typeLabelStub,\n        defaultObjectStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n        import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n        import dev.zacsweers.moshix.sealed.annotations.DefaultObject\n\n        sealed interface Animal {\n            @TypeLabel(\"dog\")\n            @JsonClass(generateAdapter = true)\n            data class Dog(val name: String) : Animal\n\n            @TypeLabel(\"cat\")\n            @JsonClass(generateAdapter = true)\n            data class Cat(val age: Int) : Animal\n\n            @DefaultObject\n            object Default : Animal\n        }\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", Animal::class.java)\n            val json = jsonInflater.deflate(model, Animal::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expect(\n        \"\"\"\n            src/test/Animal.kt:23: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                        val model = jsonInflater.inflate(\"{}\", Animal::class.java)\n                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n            src/test/Animal.kt:24: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                        val json = jsonInflater.deflate(model, Animal::class.java)\n                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n            2 errors\n      \"\"\"\n      )\n  }\n\n  @Test\n  fun testSealedClass() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        typeLabelStub,\n        defaultObjectStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n        import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n        import dev.zacsweers.moshix.sealed.annotations.DefaultObject\n\n        @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n        sealed class Animal {\n            @TypeLabel(\"dog\")\n            @JsonClass(generateAdapter = true)\n            data class Dog(val name: String) : Animal()\n\n            @TypeLabel(\"cat\")\n            @JsonClass(generateAdapter = true)\n            data class Cat(val age: Int) : Animal()\n\n            @DefaultObject\n            object Default : Animal()\n        }\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", Animal::class.java)\n            val json = jsonInflater.deflate(model, Animal::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testSealedClassMissingJsonClassAnnotation() {\n    lint()\n      .files(\n        jsonClassStub,\n        jsonInflaterStub,\n        typeLabelStub,\n        defaultObjectStub,\n        kotlin(\n          \"\"\"\n        package test\n\n        import com.squareup.moshi.JsonClass\n        import slack.commons.json.JsonInflater\n        import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n        import dev.zacsweers.moshix.sealed.annotations.DefaultObject\n\n        sealed class Animal {\n            @TypeLabel(\"dog\")\n            @JsonClass(generateAdapter = true)\n            data class Dog(val name: String) : Animal()\n\n            @TypeLabel(\"cat\")\n            @JsonClass(generateAdapter = true)\n            data class Cat(val age: Int) : Animal()\n\n            @DefaultObject\n            object Default : Animal()\n        }\n\n        fun useJsonInflater(jsonInflater: JsonInflater) {\n            val model = jsonInflater.inflate(\"{}\", Animal::class.java)\n            val json = jsonInflater.deflate(model, Animal::class.java)\n        }\n      \"\"\"\n        ),\n      )\n      .run()\n      .expect(\n        \"\"\"\n            src/test/Animal.kt:23: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                        val model = jsonInflater.inflate(\"{}\", Animal::class.java)\n                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n            src/test/Animal.kt:24: Error: Using JsonInflater.inflate/deflate with a Moshi-incompatible type. [JsonInflaterMoshiIncompatibleType]\n                        val json = jsonInflater.deflate(model, Animal::class.java)\n                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n            2 errors\n      \"\"\"\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/LintKotlinVersionCheckTest.kt",
    "content": "// Copyright (C) 2023 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Severity\nimport com.android.tools.lint.detector.api.SourceCodeScanner\nimport org.jetbrains.uast.UMethod\nimport org.junit.Test\nimport slack.lint.util.sourceImplementation\n\n/**\n * Lint chases Kotlin versions differently than what we declare our build with. This test is just\n * here to catch those for our own awareness and should be updated whenever lint updates its own.\n */\nclass LintKotlinVersionCheckTest : BaseSlackLintTest() {\n\n  override fun getDetector(): Detector = KotlinVersionDetector()\n\n  override fun getIssues() = listOf(KotlinVersionDetector.ISSUE)\n\n  @Test\n  fun check() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            package test\n\n            fun main() {\n              println(\"Hello, world!\")\n            }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n          src/test/test.kt:3: Error: Kotlin version matched expected one [KotlinVersion]\n          fun main() {\n          ^\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  class KotlinVersionDetector : Detector(), SourceCodeScanner {\n    override fun getApplicableUastTypes() = listOf(UMethod::class.java)\n\n    override fun createUastHandler(context: JavaContext): UElementHandler {\n      return object : UElementHandler() {\n        override fun visitMethod(node: UMethod) {\n          if (KotlinVersion.CURRENT == EXPECTED_VERSION) {\n            // Report something anyway to ensure our lint was correctly picked up at least\n            context.report(\n              ISSUE,\n              node,\n              context.getLocation(node),\n              \"Kotlin version matched expected one\",\n            )\n          } else {\n            context.report(\n              ISSUE,\n              node,\n              context.getLocation(node),\n              \"Kotlin version was ${KotlinVersion.CURRENT}, expected $EXPECTED_VERSION\",\n            )\n          }\n        }\n      }\n    }\n\n    companion object {\n      private val EXPECTED_VERSION = TestBuildConfig.LINT_KOTLIN_VERSION\n      val ISSUE =\n        Issue.create(\n          \"KotlinVersion\",\n          \"Kotlin version\",\n          \"Kotlin version\",\n          sourceImplementation<KotlinVersionDetector>(),\n          severity = Severity.ERROR,\n        )\n    }\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/MainScopeUsageDetectorTest.kt",
    "content": "// Copyright (C) 2020 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport org.junit.Test\n\nclass MainScopeUsageDetectorTest : BaseSlackLintTest() {\n\n  companion object {\n    private val COROUTINE_SCOPE_STUB =\n      kotlin(\n        \"test/kotlinx/coroutines/CoroutineScope.kt\",\n        // language=kotlin\n        \"\"\"\n          package kotlinx.coroutines\n\n          fun MainScope() {\n\n          }\n        \"\"\"\n          .trimIndent(),\n      )\n  }\n\n  override val skipTestModes: Array<TestMode> = arrayOf(TestMode.WHITESPACE)\n\n  override fun getDetector() = MainScopeUsageDetector()\n\n  override fun getIssues() = listOf(MainScopeUsageDetector.ISSUE)\n\n  @Test\n  fun simple() {\n    lint()\n      .files(\n        COROUTINE_SCOPE_STUB,\n        kotlin(\n            \"\"\"\n            package test.pkg\n\n            import kotlinx.coroutines.MainScope\n\n            fun example() {\n              val scope = MainScope()\n            }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .skipTestModes(TestMode.IMPORT_ALIAS)\n      .run()\n      .expect(\n        \"\"\"\n        src/test/pkg/test.kt:6: Error: Use slack.foundation.coroutines.android.MainScope. [MainScopeUsage]\n          val scope = MainScope()\n                      ~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Fix for src/test/pkg/test.kt line 6: Use slack.foundation.coroutines.android.MainScope:\n        @@ -6 +6\n        -   val scope = MainScope()\n        +   val scope = slack.foundation.coroutines.android.MainScope()\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  // Usage in tests are fine\n  @Test\n  fun testsAreFine() {\n    lint()\n      .files(\n        COROUTINE_SCOPE_STUB,\n        kotlin(\n            \"test/test/pkg/Test.kt\",\n            \"\"\"\n              package test.pkg\n\n              import kotlinx.coroutines.MainScope\n\n              fun example() {\n                val scope = MainScope()\n              }\n            \"\"\"\n              .trimIndent(),\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/MoshiEnumUsageDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport org.junit.Test\n\nclass MoshiEnumUsageDetectorTest : BaseSlackLintTest() {\n\n  private val jsonClassAnnotation =\n    java(\n        \"\"\"\n        package com.squareup.moshi;\n\n        public @interface JsonClass {\n          boolean generateAdapter();\n          String generator() default \"\";\n        }\n      \"\"\"\n      )\n      .indented()\n\n  private val jsonAnnotation =\n    java(\n        \"\"\"\n      package com.squareup.moshi;\n\n      public @interface Json {\n        String name();\n      }\n    \"\"\"\n      )\n      .indented()\n\n  override fun getDetector() = MoshiUsageDetector()\n\n  override fun getIssues() = MoshiUsageDetector.issues().toList()\n\n  @Test\n  fun java_correct() {\n    val correctJava =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.JsonClass;\n\n          @JsonClass(generateAdapter = false)\n          enum TestEnum {\n            UNKNOWN, TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(jsonClassAnnotation, jsonAnnotation, correctJava).run().expectClean()\n  }\n\n  @Test\n  fun java_expected_unknown_is_ok() {\n    val correctJava =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.JsonClass;\n          import com.squareup.moshi.Json;\n\n          @JsonClass(generateAdapter = false)\n          enum TestEnum {\n            UNKNOWN, TEST, @Json(name = \"UNKNOWN\") EXPECTED_UNKNOWN\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(jsonClassAnnotation, jsonAnnotation, correctJava).run().expectClean()\n  }\n\n  @Test\n  fun java_ignored() {\n    val source =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.JsonClass;\n\n          public enum TestEnum {\n            TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(jsonClassAnnotation, jsonAnnotation, source).run().expectClean()\n  }\n\n  @Test\n  fun java_generateAdapter_isTrue() {\n    val source =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.JsonClass;\n\n          @JsonClass(generateAdapter = true)\n          public enum TestEnum {\n            UNKNOWN, TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.java:5: Error: Enums annotated with @JsonClass must not set generateAdapter to true. [MoshiUsageEnumJsonClassGenerated]\n        @JsonClass(generateAdapter = true)\n                                     ~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Fix for src/slack/model/TestEnum.java line 5: Set to false:\n        @@ -5 +5\n        - @JsonClass(generateAdapter = true)\n        + @JsonClass(generateAdapter = false)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_custom_generator_is_ignored() {\n    val source =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.JsonClass;\n\n          @JsonClass(generateAdapter = true, generator = \"custom\")\n          public enum TestEnum {\n            TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(jsonClassAnnotation, jsonAnnotation, source).run().expectClean()\n  }\n\n  @Test\n  fun java_unknown_annotated() {\n    val source =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.JsonClass;\n          import com.squareup.moshi.Json;\n\n          @JsonClass(generateAdapter = false)\n          public enum TestEnum {\n            @Json(name = \"unknown\")\n            UNKNOWN,\n            TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.java:7: Error: UNKNOWN members in @JsonClass-annotated enums should not be annotated with @Json [MoshiUsageEnumAnnotatedUnknown]\n        public enum TestEnum {\n                    ~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_unknown_wrong_order() {\n    val source =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.JsonClass;\n\n          @JsonClass(generateAdapter = false)\n          public enum TestEnum {\n            TEST,\n            UNKNOWN\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.java:8: Error: Enums serialized with Moshi must reserve the first member as UNKNOWN. [MoshiUsageEnumMissingUnknown]\n          UNKNOWN\n          ~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_unknown_wrong_order_inferred_enum() {\n    val source =\n      java(\n          \"\"\"\n        package slack.model;\n\n        import com.squareup.moshi.JsonClass;\n        import com.squareup.moshi.Json;\n\n        @JsonClass(generateAdapter = false)\n        public enum TestEnum {\n          @Json(name = \"test\")\n          TEST,\n          UNKNOWN\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.java:10: Error: Enums serialized with Moshi must reserve the first member as UNKNOWN. [MoshiUsageEnumMissingUnknown]\n          UNKNOWN\n          ~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_json_but_missing_json_class() {\n    val source =\n      java(\n          \"\"\"\n        package slack.model;\n\n        import com.squareup.moshi.JsonClass;\n        import com.squareup.moshi.Json;\n\n        public enum TestEnum {\n          UNKNOWN,\n          @Json(name = \"test\")\n          TEST;\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.java:6: Error: Enums serialized with Moshi should be annotated with @JsonClass. [MoshiUsageEnumMissingJsonClass]\n        public enum TestEnum {\n                    ~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_lower_casing() {\n    val source =\n      java(\n          \"\"\"\n        package slack.model;\n\n        import com.squareup.moshi.JsonClass;\n\n        @JsonClass(generateAdapter = false)\n        enum TestEnum {\n          UNKNOWN,\n          test\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/TestEnum.java:8: Warning: Consider using @Json(name = ...) rather than lower casing. [MoshiUsageEnumCasing]\n            test\n            ~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_lower_casing_already_annotated() {\n    val source =\n      java(\n          \"\"\"\n        package slack.model;\n\n        import com.squareup.moshi.Json;\n        import com.squareup.moshi.JsonClass;\n\n        @JsonClass(generateAdapter = false)\n        enum TestEnum {\n          UNKNOWN,\n          @Json(name = \"taken\") test\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/TestEnum.java:9: Warning: Consider using @Json(name = ...) rather than lower casing. [MoshiUsageEnumCasing]\n            @Json(name = \"taken\") test\n                                  ~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Fix for src/slack/model/TestEnum.java line 9: Rename to 'TEST':\n        @@ -9 +9\n        -   @Json(name = \"taken\") test\n        +   @Json(name = \"taken\") TEST\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_json_name_blank() {\n    val source =\n      java(\n          \"\"\"\n        package slack.model;\n\n        import com.squareup.moshi.Json;\n        import com.squareup.moshi.JsonClass;\n\n        @JsonClass(generateAdapter = false)\n        enum TestEnum {\n          UNKNOWN,\n          @Json(name = \" \") TEST\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/TestEnum.java:9: Error: Don't use blank names in @Json. [MoshiUsageBlankJsonName]\n            @Json(name = \" \") TEST\n                         ~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_json_name_empty() {\n    val source =\n      java(\n          \"\"\"\n        package slack.model;\n\n        import com.squareup.moshi.Json;\n        import com.squareup.moshi.JsonClass;\n\n        @JsonClass(generateAdapter = false)\n        enum TestEnum {\n          UNKNOWN,\n          @Json(name = \"\") TEST\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/TestEnum.java:9: Error: Don't use blank names in @Json. [MoshiUsageBlankJsonName]\n            @Json(name = \"\") TEST\n                         ~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_redundantJsonName() {\n    val source =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.Json;\n          import com.squareup.moshi.JsonClass;\n\n          @JsonClass(generateAdapter = false)\n          public enum Example {\n            UNKNOWN,\n            @Json(name = \"VALUE\") VALUE\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.java:9: Warning: Json.name with the same value as the property/enum member name is redundant. [MoshiUsageRedundantJsonName]\n            @Json(name = \"VALUE\") VALUE\n                         ~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Fix for src/slack/model/Example.java line 9: Remove '@Json(name = \"VALUE\")':\n        @@ -9 +9\n        -   @Json(name = \"VALUE\") VALUE\n        +    VALUE\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_duplicateJsonNames() {\n    val source =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.Json;\n          import com.squareup.moshi.JsonClass;\n\n          @JsonClass(generateAdapter = false)\n          public enum Example {\n            UNKNOWN,\n            VALUE,\n            @Json(name = \"VALUE\") VALUE_1,\n            @Json(name = \"value2\") VALUE_2,\n            @Json(name = \"value2\") VALUE_3;\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonAnnotation, jsonClassAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.java:9: Error: Name 'VALUE' is duplicated by member 'VALUE_1'. [MoshiUsageDuplicateJsonName]\n            VALUE,\n            ~~~~~\n          src/slack/model/Example.java:10: Error: Name 'VALUE' is duplicated by member 'VALUE'. [MoshiUsageDuplicateJsonName]\n            @Json(name = \"VALUE\") VALUE_1,\n                                  ~~~~~~~\n          src/slack/model/Example.java:11: Error: Name 'value2' is duplicated by member 'VALUE_3'. [MoshiUsageDuplicateJsonName]\n            @Json(name = \"value2\") VALUE_2,\n                                   ~~~~~~~\n          src/slack/model/Example.java:12: Error: Name 'value2' is duplicated by member 'VALUE_2'. [MoshiUsageDuplicateJsonName]\n            @Json(name = \"value2\") VALUE_3;\n                                   ~~~~~~~\n          4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_correct() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = false)\n          enum class TestEnum {\n            UNKNOWN, TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(jsonClassAnnotation, jsonAnnotation, source).run().expectClean()\n  }\n\n  @Test\n  fun kotlin_expected_unknown_is_ok() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import com.squareup.moshi.Json\n\n          @JsonClass(generateAdapter = false)\n          enum class TestEnum {\n            UNKNOWN, TEST, @Json(name = \"UNKNOWN\") EXPECTED_UNKNOWN\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(jsonClassAnnotation, jsonAnnotation, source).run().expectClean()\n  }\n\n  @Test\n  fun kotlin_ignored() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          enum class TestEnum {\n            TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(jsonClassAnnotation, jsonAnnotation, source).run().expectClean()\n  }\n\n  @Test\n  fun kotlin_generateAdapter_isTrue() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          enum class TestEnum {\n            UNKNOWN, TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.kt:5: Error: Enums annotated with @JsonClass must not set generateAdapter to true. [MoshiUsageEnumJsonClassGenerated]\n        @JsonClass(generateAdapter = true)\n                                     ~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Fix for src/slack/model/TestEnum.kt line 5: Set to false:\n        @@ -5 +5\n        - @JsonClass(generateAdapter = true)\n        + @JsonClass(generateAdapter = false)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_custom_generator_is_ignored() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true, generator = \"custom\")\n          enum class TestEnum {\n            TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(jsonClassAnnotation, jsonAnnotation, source).run().expectClean()\n  }\n\n  @Test\n  fun kotlin_unknown_annotated() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import com.squareup.moshi.Json\n\n          @JsonClass(generateAdapter = false)\n          enum class TestEnum {\n            @Json(name = \"unknown\")\n            UNKNOWN,\n            TEST\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.kt:7: Error: UNKNOWN members in @JsonClass-annotated enums should not be annotated with @Json [MoshiUsageEnumAnnotatedUnknown]\n        enum class TestEnum {\n                   ~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_unknown_wrong_order() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = false)\n          enum class TestEnum {\n            TEST,\n            UNKNOWN\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.kt:8: Error: Enums serialized with Moshi must reserve the first member as UNKNOWN. [MoshiUsageEnumMissingUnknown]\n          UNKNOWN\n          ~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_unknown_wrong_order_inferred_enum() {\n    val source =\n      kotlin(\n          \"\"\"\n        package slack.model\n\n        import com.squareup.moshi.JsonClass\n        import com.squareup.moshi.Json\n\n        @JsonClass(generateAdapter = false)\n        enum class TestEnum {\n          @Json(name = \"test\")\n          TEST,\n          UNKNOWN\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.kt:10: Error: Enums serialized with Moshi must reserve the first member as UNKNOWN. [MoshiUsageEnumMissingUnknown]\n          UNKNOWN\n          ~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_json_but_missing_json_class() {\n    val source =\n      kotlin(\n          \"\"\"\n        package slack.model\n\n        import com.squareup.moshi.JsonClass\n        import com.squareup.moshi.Json\n\n        enum class TestEnum {\n          UNKNOWN,\n          @Json(name = \"test\")\n          TEST\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/TestEnum.kt:6: Error: Enums serialized with Moshi should be annotated with @JsonClass. [MoshiUsageEnumMissingJsonClass]\n        enum class TestEnum {\n                   ~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_lower_casing() {\n    val source =\n      kotlin(\n          \"\"\"\n        package slack.model\n\n        import com.squareup.moshi.JsonClass\n\n        @JsonClass(generateAdapter = false)\n        enum class TestEnum {\n          UNKNOWN,\n          test\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/TestEnum.kt:8: Warning: Consider using @Json(name = ...) rather than lower casing. [MoshiUsageEnumCasing]\n            test\n            ~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Fix for src/slack/model/TestEnum.kt line 8: Add @Json(name = \"test\") and rename to 'TEST':\n        @@ -8 +8\n        -   test\n        +   @com.squareup.moshi.Json(name = \"test\") TEST\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_lower_casing_already_annotated() {\n    val source =\n      kotlin(\n          \"\"\"\n        package slack.model\n\n        import com.squareup.moshi.Json\n        import com.squareup.moshi.JsonClass\n\n        @JsonClass(generateAdapter = false)\n        enum class TestEnum {\n          UNKNOWN,\n          @Json(name = \"taken\") test\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/TestEnum.kt:9: Warning: Consider using @Json(name = ...) rather than lower casing. [MoshiUsageEnumCasing]\n            @Json(name = \"taken\") test\n                                  ~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Fix for src/slack/model/TestEnum.kt line 9: Rename to 'TEST':\n        @@ -9 +9\n        -   @Json(name = \"taken\") test\n        +   @Json(name = \"taken\") TEST\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_json_name_blank() {\n    val source =\n      kotlin(\n          \"\"\"\n        package slack.model\n\n        import com.squareup.moshi.Json\n        import com.squareup.moshi.JsonClass\n\n        @JsonClass(generateAdapter = false)\n        enum class TestEnum {\n          UNKNOWN,\n          @Json(name = \" \") TEST\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/TestEnum.kt:9: Error: Don't use blank names in @Json. [MoshiUsageBlankJsonName]\n            @Json(name = \" \") TEST\n                         ~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_json_name_empty() {\n    val source =\n      kotlin(\n          \"\"\"\n        package slack.model\n\n        import com.squareup.moshi.Json\n        import com.squareup.moshi.JsonClass\n\n        @JsonClass(generateAdapter = false)\n        enum class TestEnum {\n          UNKNOWN,\n          @Json(name = \"\") TEST\n        }\n      \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/TestEnum.kt:9: Error: Don't use blank names in @Json. [MoshiUsageBlankJsonName]\n            @Json(name = \"\") TEST\n                         ~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_redundantJsonName() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.Json\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = false)\n          enum class Example {\n            UNKNOWN,\n            @Json(name = \"VALUE\") VALUE\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonClassAnnotation, jsonAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:9: Warning: Json.name with the same value as the property/enum member name is redundant. [MoshiUsageRedundantJsonName]\n            @Json(name = \"VALUE\") VALUE\n                         ~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Fix for src/slack/model/Example.kt line 9: Remove '@Json(name = \"VALUE\")':\n        @@ -9 +9\n        -   @Json(name = \"VALUE\") VALUE\n        +    VALUE\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_duplicateJsonNames() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.Json\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = false)\n          enum class Example {\n            UNKNOWN,\n            VALUE,\n            @Json(name = \"VALUE\") VALUE_1,\n            @Json(name = \"value2\") VALUE_2,\n            @Json(name = \"value2\") VALUE_3\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(jsonAnnotation, jsonClassAnnotation, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:9: Error: Name 'VALUE' is duplicated by member 'VALUE_1'. [MoshiUsageDuplicateJsonName]\n            VALUE,\n            ~~~~~\n          src/slack/model/Example.kt:10: Error: Name 'VALUE' is duplicated by member 'VALUE'. [MoshiUsageDuplicateJsonName]\n            @Json(name = \"VALUE\") VALUE_1,\n                                  ~~~~~~~\n          src/slack/model/Example.kt:11: Error: Name 'value2' is duplicated by member 'VALUE_3'. [MoshiUsageDuplicateJsonName]\n            @Json(name = \"value2\") VALUE_2,\n                                   ~~~~~~~\n          src/slack/model/Example.kt:12: Error: Name 'value2' is duplicated by member 'VALUE_2'. [MoshiUsageDuplicateJsonName]\n            @Json(name = \"value2\") VALUE_3\n                                   ~~~~~~~\n          4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/MoshiUsageDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport org.junit.Test\n\nclass MoshiUsageDetectorTest : BaseSlackLintTest() {\n\n  private val keepAnnotation =\n    java(\n        \"\"\"\n        package androidx.annotation;\n\n        public @interface Keep {\n        }\n      \"\"\"\n      )\n      .indented()\n\n  private val jsonClassAnnotation =\n    java(\n        \"\"\"\n        package com.squareup.moshi;\n\n        public @interface JsonClass {\n          boolean generateAdapter();\n          String generator() default \"\";\n        }\n      \"\"\"\n      )\n      .indented()\n\n  private val jsonAnnotation =\n    java(\n        \"\"\"\n      package com.squareup.moshi;\n\n      public @interface Json {\n        String name();\n      }\n    \"\"\"\n      )\n      .indented()\n\n  private val jsonQualifierAnnotation =\n    java(\n        \"\"\"\n      package com.squareup.moshi;\n\n      public @interface JsonQualifier {\n      }\n    \"\"\"\n      )\n      .indented()\n\n  private val typeLabel =\n    kotlin(\n        \"\"\"\n      package dev.zacsweers.moshix.sealed.annotations\n\n      annotation class TypeLabel(val label: String, val alternateLabels: Array<String> = [])\n    \"\"\"\n      )\n      .indented()\n\n  private val defaultObject =\n    kotlin(\n        \"\"\"\n      package dev.zacsweers.moshix.sealed.annotations\n\n      annotation class DefaultObject\n    \"\"\"\n      )\n      .indented()\n\n  private val adaptedBy =\n    kotlin(\n        \"\"\"\n      package dev.zacsweers.moshix.adapters\n\n      import kotlin.reflect.KClass\n\n      annotation class AdaptedBy(\n        val adapter: KClass<*>,\n        val nullSafe: Boolean = true\n      )\n    \"\"\"\n      )\n      .indented()\n\n  private val jsonAdapter =\n    java(\n        \"\"\"\n      package com.squareup.moshi;\n\n      public class JsonAdapter<T> {\n        public interface Factory {\n\n        }\n      }\n    \"\"\"\n      )\n      .indented()\n\n  override val skipTestModes: Array<TestMode> =\n    arrayOf(\n      TestMode.WHITESPACE,\n      TestMode.SUPPRESSIBLE,\n      // Aliases are impossible to test correctly because you have to maintain completely different\n      // expected fixes and source inputs\n      TestMode.TYPE_ALIAS,\n      TestMode.IMPORT_ALIAS,\n    )\n\n  override fun getDetector() = MoshiUsageDetector()\n\n  override fun getIssues() = MoshiUsageDetector.issues().toList()\n\n  @Test\n  fun simpleCorrect() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import com.squareup.moshi.Json\n\n          @JsonClass(generateAdapter = true)\n          data class TestClass(@Json(name = \"bar\") val foo: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(*testFiles(), source).run().expectClean()\n  }\n\n  @Test\n  fun sealed_correct() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n          import dev.zacsweers.moshix.sealed.annotations.DefaultObject\n\n          @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n          sealed class BaseType {\n            @TypeLabel(label = \"nested\")\n            @JsonClass(generateAdapter = true)\n            data class Nested(val foo: String) : BaseType()\n          }\n\n          @TypeLabel(label = \"one\")\n          @JsonClass(generateAdapter = true)\n          data class Subtype(val foo: String) : BaseType()\n\n          @TypeLabel(label = \"two\")\n          object ObjectSubType : BaseType()\n\n          @DefaultObject\n          object Default : BaseType()\n\n          // Cover for making sure listing interfaces before superclasses don't affect\n          // superclass lookups\n          @TypeLabel(label = \"three\")\n          @JsonClass(generateAdapter = true)\n          data class SubtypeWithInterface(val foo: String) : ARandomInterface, BaseType()\n\n          interface ARandomInterface\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(*testFiles(), source).run().expectClean()\n  }\n\n  @Test\n  fun sealed_interface_correct() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n          import dev.zacsweers.moshix.sealed.annotations.DefaultObject\n\n          @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n          sealed interface BaseType {\n            @TypeLabel(label = \"nested\")\n            @JsonClass(generateAdapter = true)\n            data class Nested(val foo: String) : BaseType\n          }\n\n          @TypeLabel(label = \"one\")\n          @JsonClass(generateAdapter = true)\n          data class Subtype(val foo: String) : BaseType\n\n          @TypeLabel(label = \"two\")\n          object ObjectSubType : BaseType\n\n          @DefaultObject\n          object Default : BaseType\n\n          // Cover for making sure listing interfaces before superclasses don't affect\n          // superclass lookups\n          @TypeLabel(label = \"three\")\n          @JsonClass(generateAdapter = true)\n          data class SubtypeWithInterface(val foo: String) : ARandomInterface, BaseType\n\n          interface ARandomInterface\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(*testFiles(), source).run().expectClean()\n  }\n\n  @Test\n  fun sealed_generic() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n\n          @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n          sealed class BaseType<T>\n\n          @TypeLabel(label = \"one\")\n          @JsonClass(generateAdapter = true)\n          data class Subtype<T>(val foo: T) : BaseType<T>()\n\n          // This is \"ok\" generics use because the subtype itself has none\n          @TypeLabel(label = \"two\")\n          @JsonClass(generateAdapter = true)\n          data class SubtypeTwo(val foo: String) : BaseType<String>()\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/BaseType.kt:11: Error: Sealed subtypes used with moshi-sealed cannot be generic. [MoshiUsageGenericSealedSubtype]\n        data class Subtype<T>(val foo: T) : BaseType<T>()\n                          ~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun sealed_missing_base_type() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n          import dev.zacsweers.moshix.sealed.annotations.DefaultObject\n\n          @TypeLabel(label = \"one\")\n          @JsonClass(generateAdapter = true)\n          data class Subtype(val foo: String)\n\n          @TypeLabel(label = \"two\")\n          object ObjectSubType\n\n          @DefaultObject\n          object Default\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Subtype.kt:7: Error: Inappropriate @TypeLabel or @DefaultObject annotation. [MoshiUsageInappropriateTypeLabel]\n        @TypeLabel(label = \"one\")\n        ~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Subtype.kt:11: Error: Inappropriate @TypeLabel or @DefaultObject annotation. [MoshiUsageInappropriateTypeLabel]\n        @TypeLabel(label = \"two\")\n        ~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Subtype.kt:14: Error: Inappropriate @TypeLabel or @DefaultObject annotation. [MoshiUsageInappropriateTypeLabel]\n        @DefaultObject\n        ~~~~~~~~~~~~~~\n        3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Subtype.kt line 7: Remove '@TypeLabel(label = \"one\")':\n        @@ -7 +7\n        - @TypeLabel(label = \"one\")\n        Autofix for src/slack/model/Subtype.kt line 11: Remove '@TypeLabel(label = \"two\")':\n        @@ -11 +11\n        - @TypeLabel(label = \"two\")\n        Autofix for src/slack/model/Subtype.kt line 14: Remove '@DefaultObject':\n        @@ -14 +14\n        - @DefaultObject\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun sealed_double_annotation() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import dev.zacsweers.moshix.sealed.annotations.TypeLabel\n          import dev.zacsweers.moshix.sealed.annotations.DefaultObject\n\n          @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n          sealed class BaseType\n\n          @TypeLabel(label = \"one\")\n          @DefaultObject\n          object Default : BaseType()\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/BaseType.kt:10: Error: Only use one of @TypeLabel or @DefaultObject. [MoshiUsageDoubleTypeLabel]\n        @TypeLabel(label = \"one\")\n        ~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/BaseType.kt:11: Error: Only use one of @TypeLabel or @DefaultObject. [MoshiUsageDoubleTypeLabel]\n        @DefaultObject\n        ~~~~~~~~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/BaseType.kt line 10: Remove '@TypeLabel(label = \"one\")':\n        @@ -10 +10\n        - @TypeLabel(label = \"one\")\n        Autofix for src/slack/model/BaseType.kt line 11: Remove '@DefaultObject':\n        @@ -11 +11\n        - @DefaultObject\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun sealed_missing_type_label() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n          sealed class BaseType\n\n          @JsonClass(generateAdapter = true)\n          data class Subtype(val foo: String) : BaseType()\n\n          object ObjectSubType : BaseType()\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/BaseType.kt:9: Error: Sealed Moshi subtypes must be annotated with @TypeLabel or @DefaultObject. [MoshiUsageMissingTypeLabel]\n        data class Subtype(val foo: String) : BaseType()\n                   ~~~~~~~\n        src/slack/model/BaseType.kt:11: Error: Sealed Moshi subtypes must be annotated with @TypeLabel or @DefaultObject. [MoshiUsageMissingTypeLabel]\n        object ObjectSubType : BaseType()\n               ~~~~~~~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun sealed_must_be_sealed() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n          abstract class BaseType\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/BaseType.kt:6: Error: Moshi-sealed can only be applied to 'sealed' types. [MoshiUsageSealedMustBeSealed]\n        abstract class BaseType\n                       ~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun sealed_blank_type() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true, generator = \"sealed:\")\n          sealed class BaseType\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/BaseType.kt:5: Error: Moshi-sealed requires a type label specified after the 'sealed:' prefix. [MoshiUsageBlankTypeLabel]\n        @JsonClass(generateAdapter = true, generator = \"sealed:\")\n                                                       ~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun empty_generator() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true, generator = \"\")\n          data class Example(val value: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:5: Error: Don't use blank JsonClass.generator values. [MoshiUsageBlankGenerator]\n          @JsonClass(generateAdapter = true, generator = \"\")\n                                                         ~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun blank_generator() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true, generator = \" \")\n          data class Example(val value: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:5: Error: Don't use blank JsonClass.generator values. [MoshiUsageBlankGenerator]\n        @JsonClass(generateAdapter = true, generator = \" \")\n                                                       ~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun private_constructor() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          data class Example private constructor(val value: String)\n\n          @JsonClass(generateAdapter = true)\n          data class Example2 protected constructor(val value: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:6: Error: Constructors in Moshi classes cannot be private. [MoshiUsagePrivateConstructor]\n        data class Example private constructor(val value: String)\n                           ~~~~~~~\n        src/slack/model/Example.kt:9: Error: Constructors in Moshi classes cannot be private. [MoshiUsagePrivateConstructor]\n        data class Example2 protected constructor(val value: String)\n                            ~~~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 6: Make constructor 'internal':\n        @@ -6 +6\n        - data class Example private constructor(val value: String)\n        + data class Example internal constructor(val value: String)\n        Autofix for src/slack/model/Example.kt line 9: Make constructor 'internal':\n        @@ -9 +9\n        - data class Example2 protected constructor(val value: String)\n        @@ -10 +9\n        + data class Example2 internal constructor(val value: String)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun params_that_need_init() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import kotlin.jvm.Transient\n\n          @JsonClass(generateAdapter = true)\n          class Example(val value: String, nonProp: String, @Transient val transientProp: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:7: Error: Constructor non-property parameters in Moshi classes must have default values. [MoshiUsageParamNeedsInit]\n        class Example(val value: String, nonProp: String, @Transient val transientProp: String)\n                                         ~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:7: Error: Transient constructor properties must have default values. [MoshiUsageTransientNeedsInit]\n        class Example(val value: String, nonProp: String, @Transient val transientProp: String)\n                                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:7: Error: Model classes should be immutable data classes. [MoshiUsageUseData]\n        class Example(val value: String, nonProp: String, @Transient val transientProp: String)\n              ~~~~~~~\n        3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun private_prop() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          data class Example(private val value: String, protected val value2: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:6: Error: Constructor parameter properties in Moshi classes cannot be private. [MoshiUsagePrivateConstructorProperty]\n        data class Example(private val value: String, protected val value2: String)\n                           ~~~~~~~\n        src/slack/model/Example.kt:6: Error: Constructor parameter properties in Moshi classes cannot be private. [MoshiUsagePrivateConstructorProperty]\n        data class Example(private val value: String, protected val value2: String)\n                                                      ~~~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 6: Make value 'internal':\n        @@ -6 +6\n        - data class Example(private val value: String, protected val value2: String)\n        @@ -7 +6\n        + data class Example(internal val value: String, protected val value2: String)\n        Autofix for src/slack/model/Example.kt line 6: Make value2 'internal':\n        @@ -6 +6\n        - data class Example(private val value: String, protected val value2: String)\n        @@ -7 +6\n        + data class Example(private val value: String, internal val value2: String)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun mutable_prop() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          data class Example(var value: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:6: Warning: Moshi properties should be immutable. [MoshiUsageVarProperty]\n        data class Example(var value: String)\n                           ~~~\n        0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 6: Make value 'val':\n        @@ -6 +6\n        - data class Example(var value: String)\n        @@ -7 +6\n        + data class Example(val value: String)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun generateAdapter_should_be_true() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = false)\n          data class Example(val value: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:5: Error: JsonClass.generateAdapter must be true in order for Moshi code gen to run. [MoshiUsageGenerateAdapterShouldBeTrue]\n        @JsonClass(generateAdapter = false)\n                                     ~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun empty_json_name() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import com.squareup.moshi.Json\n\n          @JsonClass(generateAdapter = true)\n          data class Example(@Json(name = \"\") val value: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:7: Error: Don't use blank names in @Json. [MoshiUsageBlankJsonName]\n          data class Example(@Json(name = \"\") val value: String)\n                                          ~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun blank_json_name() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import com.squareup.moshi.Json\n\n          @JsonClass(generateAdapter = true)\n          data class Example(@Json(name = \" \") val value: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:7: Error: Don't use blank names in @Json. [MoshiUsageBlankJsonName]\n          data class Example(@Json(name = \" \") val value: String)\n                                          ~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun jsonNameSiteTargets() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import com.squareup.moshi.Json\n\n          @JsonClass(generateAdapter = true)\n          data class Example(\n            @field:Json(name = \"foo\") val value: String\n          )\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:8: Error: Use of site-targets on @Json are redundant. [MoshiUsageRedundantSiteTarget]\n            @field:Json(name = \"foo\") val value: String\n             ~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 8: Remove 'field:':\n        @@ -8 +8\n        -   @field:Json(name = \"foo\") val value: String\n        +   @Json(name = \"foo\") val value: String\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun jsonNameMultiple() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import com.squareup.moshi.Json\n\n          @JsonClass(generateAdapter = true)\n          data class Example(\n            @Json(name = \"foo\") @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n          )\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:8: Error: Use of site-targets on @Json are redundant. [MoshiUsageRedundantSiteTarget]\n            @Json(name = \"foo\") @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n                                ~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/model/Example.kt:8: Error: Use of site-targets on @Json are redundant. [MoshiUsageRedundantSiteTarget]\n            @Json(name = \"foo\") @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n                                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/model/Example.kt:8: Error: Use of site-targets on @Json are redundant. [MoshiUsageRedundantSiteTarget]\n            @Json(name = \"foo\") @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n                                                                                       ~~~~~~~~~~~~~~~~~~~~~~~\n          3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 8: Remove '@field:Json(name = \"foo\")':\n        @@ -8 +8\n        -   @Json(name = \"foo\") @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n        +   @Json(name = \"foo\")  @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n        Autofix for src/slack/model/Example.kt line 8: Remove '@property:Json(name = \"foo\")':\n        @@ -8 +8\n        -   @Json(name = \"foo\") @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n        +   @Json(name = \"foo\") @field:Json(name = \"foo\")  @get:Json(name = \"foo\") val value: String\n        Autofix for src/slack/model/Example.kt line 8: Remove '@get:Json(name = \"foo\")':\n        @@ -8 +8\n        -   @Json(name = \"foo\") @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n        +   @Json(name = \"foo\") @field:Json(name = \"foo\") @property:Json(name = \"foo\")  val value: String\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  // Tweaked multiple site targets test, where we intentionally leave one for a secondary cleanup\n  @Test\n  fun jsonNameMultipleAllSiteTargets() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import com.squareup.moshi.Json\n\n          @JsonClass(generateAdapter = true)\n          data class Example(\n            @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n          )\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:8: Error: Use of site-targets on @Json are redundant. [MoshiUsageRedundantSiteTarget]\n            @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/model/Example.kt:8: Error: Use of site-targets on @Json are redundant. [MoshiUsageRedundantSiteTarget]\n            @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n                                                                   ~~~~~~~~~~~~~~~~~~~~~~~\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 8: Remove '@property:Json(name = \"foo\")':\n        @@ -8 +8\n        -   @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n        +   @field:Json(name = \"foo\")  @get:Json(name = \"foo\") val value: String\n        Autofix for src/slack/model/Example.kt line 8: Remove '@get:Json(name = \"foo\")':\n        @@ -8 +8\n        -   @field:Json(name = \"foo\") @property:Json(name = \"foo\") @get:Json(name = \"foo\") val value: String\n        +   @field:Json(name = \"foo\") @property:Json(name = \"foo\")  val value: String\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun snake_case_name() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n          import com.squareup.moshi.Json\n\n          @JsonClass(generateAdapter = true)\n          data class Example(\n            val snake_case: String,\n            @Json(name = \"taken\") val already_annotated_is_ignored: String\n          )\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:8: Warning: Consider using @Json(name = ...) rather than direct snake casing. [MoshiUsageSnakeCase]\n          val snake_case: String,\n              ~~~~~~~~~~\n        0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 8: Add @Json(name = \"snake_case\") and rename to 'snakeCase':\n        @@ -8 +8\n        -   val snake_case: String,\n        +   @Json(name = \"snake_case\") val snakeCase: String,\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun missing_primary() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          class Example\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:6: Error: @JsonClass-annotated types must have a primary constructor or be sealed. [MoshiUsageMissingPrimary]\n        class Example\n              ~~~~~~~\n        src/slack/model/Example.kt:6: Error: Model classes should be immutable data classes. [MoshiUsageUseData]\n        class Example\n              ~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun missing_primary_ok_in_sealed() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true, generator = \"sealed:type\")\n          sealed class Example\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(*testFiles(), source).run().expectClean()\n  }\n\n  @Test\n  fun unsupportedClasses() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          class Example {\n            @JsonClass(generateAdapter = true)\n            annotation class UnsupportedAnnotation\n\n            @JsonClass(generateAdapter = true)\n            inner class UnsupportedInner\n\n            @JsonClass(generateAdapter = true)\n            abstract class UnsupportedAbstract\n\n            @JsonClass(generateAdapter = true)\n            interface UnsupportedInterface\n\n            @JsonClass(generateAdapter = false)\n            interface UnsupportedInterfaceButGenerateAdapterIsFalseIsOk\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:6: Error: This type cannot be annotated with @JsonClass. [MoshiUsageUnsupportedType]\n          @JsonClass(generateAdapter = true)\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:9: Error: This type cannot be annotated with @JsonClass. [MoshiUsageUnsupportedType]\n          @JsonClass(generateAdapter = true)\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:12: Error: This type cannot be annotated with @JsonClass. [MoshiUsageUnsupportedType]\n          @JsonClass(generateAdapter = true)\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:15: Error: This type cannot be annotated with @JsonClass. [MoshiUsageUnsupportedType]\n          @JsonClass(generateAdapter = true)\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 6: Remove '@JsonClass(generateAdapter = true)':\n        @@ -6 +6\n        -   @JsonClass(generateAdapter = true)\n        Autofix for src/slack/model/Example.kt line 9: Remove '@JsonClass(generateAdapter = true)':\n        @@ -9 +9\n        -   @JsonClass(generateAdapter = true)\n        Autofix for src/slack/model/Example.kt line 12: Remove '@JsonClass(generateAdapter = true)':\n        @@ -12 +12\n        -   @JsonClass(generateAdapter = true)\n        Autofix for src/slack/model/Example.kt line 15: Remove '@JsonClass(generateAdapter = true)':\n        @@ -15 +15\n        -   @JsonClass(generateAdapter = true)\n        \"\"\"\n          .trimIndent()\n          // Weirdness here because spotless strips the trailing whitespace after the '+'\n          .lineSequence()\n          .map { line ->\n            if (line == \"+\") {\n              \"+  \"\n            } else {\n              line\n            }\n          }\n          .joinToString(\"\\n\")\n      )\n  }\n\n  @Test\n  fun unsupportedClasses_okWithAdaptedBy() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import androidx.annotation.Keep\n          import com.squareup.moshi.JsonAdapter\n          import dev.zacsweers.moshix.adapters.AdaptedBy\n\n          class Example {\n            @AdaptedBy(CustomFactory::class)\n            annotation class UnsupportedAnnotation\n\n            @AdaptedBy(CustomFactory::class)\n            inner class UnsupportedInner\n\n            @AdaptedBy(CustomFactory::class)\n            abstract class UnsupportedAbstract\n\n            @AdaptedBy(CustomFactory::class)\n            interface UnsupportedInterface\n          }\n\n          @Keep\n          abstract class CustomFactory : JsonAdapter.Factory\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(*testFiles(), source).run().expectClean()\n  }\n\n  @Test\n  fun double_class_annotation() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import androidx.annotation.Keep\n          import com.squareup.moshi.JsonAdapter\n          import com.squareup.moshi.JsonClass\n          import dev.zacsweers.moshix.adapters.AdaptedBy\n\n          @JsonClass(generateAdapter = true)\n          @AdaptedBy(CustomFactory::class)\n          data class Example(val value: String)\n\n          @Keep\n          abstract class CustomFactory : JsonAdapter.Factory\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:8: Error: Only use one of @AdaptedBy or @JsonClass. [MoshiUsageDoubleClassAnnotation]\n        @JsonClass(generateAdapter = true)\n        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:9: Error: Only use one of @AdaptedBy or @JsonClass. [MoshiUsageDoubleClassAnnotation]\n        @AdaptedBy(CustomFactory::class)\n        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 8: Remove '@JsonClass(generateAdapter = true)':\n        @@ -8 +8\n        - @JsonClass(generateAdapter = true)\n        Autofix for src/slack/model/Example.kt line 9: Remove '@AdaptedBy(CustomFactory::class)':\n        @@ -9 +9\n        - @AdaptedBy(CustomFactory::class)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun valid_adapters() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import androidx.annotation.Keep\n          import com.squareup.moshi.JsonAdapter\n          import com.squareup.moshi.JsonClass\n          import dev.zacsweers.moshix.adapters.AdaptedBy\n\n          @AdaptedBy(CustomFactory::class)\n          class Example1(val value: String)\n\n          @AdaptedBy(CustomAdapter::class)\n          class Example2(val value: String)\n\n          @AdaptedBy(NotAnAdapter::class)\n          class Example3\n\n          @JsonClass(generateAdapter = true)\n          data class Example4(\n            @AdaptedBy(CustomAdapter::class) val value1: String,\n            @AdaptedBy(NotAnAdapter::class) val value2: String\n          )\n\n          @AdaptedBy(CustomAdapterMissingKeep::class)\n          class Example5\n\n          @Keep\n          abstract class CustomFactory : JsonAdapter.Factory\n          @Keep\n          abstract class CustomAdapter : JsonAdapter<String>()\n          class NotAnAdapter\n          abstract class CustomAdapterMissingKeep : JsonAdapter<String>()\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example1.kt:14: Error: @AdaptedBy.adapter must be a JsonAdapter or JsonAdapter.Factory. [MoshiUsageAdaptedByRequiresAdapter]\n        @AdaptedBy(NotAnAdapter::class)\n                   ~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example1.kt:20: Error: @AdaptedBy.adapter must be a JsonAdapter or JsonAdapter.Factory. [MoshiUsageAdaptedByRequiresAdapter]\n          @AdaptedBy(NotAnAdapter::class) val value2: String\n                     ~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example1.kt:23: Error: Adapters targeted by @AdaptedBy must have @Keep. [MoshiUsageAdaptedByRequiresKeep]\n        @AdaptedBy(CustomAdapterMissingKeep::class)\n                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun unsupportedClasses_okWithCustomGenerator() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          class Example {\n            @JsonClass(generateAdapter = true, generator = \"custom\")\n            annotation class UnsupportedAnnotation\n\n            @JsonClass(generateAdapter = true, generator = \"custom\")\n            inner class UnsupportedInner\n\n            @JsonClass(generateAdapter = true, generator = \"custom\")\n            abstract class UnsupportedAbstract\n\n            @JsonClass(generateAdapter = true, generator = \"custom\")\n            interface UnsupportedInterface\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(*testFiles(), source).run().expectClean()\n  }\n\n  @Test\n  fun unsupported_visibility() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          private data class PrivateClass(val value: String)\n\n          open class EnclosingClass {\n            @JsonClass(generateAdapter = true)\n            protected data class ProtectedClass(val value: String)\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/PrivateClass.kt:6: Error: @JsonClass-annotated types must be public, package-private, or internal. [MoshiUsageClassVisibility]\n        private data class PrivateClass(val value: String)\n        ~~~~~~~\n        src/slack/model/PrivateClass.kt:10: Error: @JsonClass-annotated types must be public, package-private, or internal. [MoshiUsageClassVisibility]\n          protected data class ProtectedClass(val value: String)\n          ~~~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/PrivateClass.kt line 6: Make 'internal':\n        @@ -6 +6\n        - private data class PrivateClass(val value: String)\n        + internal data class PrivateClass(val value: String)\n        Autofix for src/slack/model/PrivateClass.kt line 10: Make 'internal':\n        @@ -10 +10\n        -   protected data class ProtectedClass(val value: String)\n        +   internal data class ProtectedClass(val value: String)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun enum_prop_suggest_moshi() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          data class Example(val value: TestEnum)\n\n          enum class TestEnum {\n            VALUE\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:6: Warning: Consider making enum properties also use Moshi. [MoshiUsageEnumPropertyCouldBeMoshi]\n        data class Example(val value: TestEnum)\n                                      ~~~~~~~~\n        0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun enum_prop_default_unknown() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          data class Example(\n            val value1: TestEnum?,\n            val value2: TestEnum? = null,\n            val value3: TestEnum? = UNKNOWN,\n            val value4: TestEnum = UNKNOWN,\n            val value5: TestEnum = TestEnum.UNKNOWN,\n          )\n\n          @JsonClass(generateAdapter = false)\n          enum class TestEnum {\n            UNKNOWN, VALUE\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:9: Error: Suspicious default value to 'UNKNOWN' for a Moshi enum. [MoshiUsageEnumPropertyDefaultUnknown]\n          val value3: TestEnum? = UNKNOWN,\n                                  ~~~~~~~\n        src/slack/model/Example.kt:10: Error: Suspicious default value to 'UNKNOWN' for a Moshi enum. [MoshiUsageEnumPropertyDefaultUnknown]\n          val value4: TestEnum = UNKNOWN,\n                                 ~~~~~~~\n        src/slack/model/Example.kt:11: Error: Suspicious default value to 'UNKNOWN' for a Moshi enum. [MoshiUsageEnumPropertyDefaultUnknown]\n          val value5: TestEnum = TestEnum.UNKNOWN,\n                                 ~~~~~~~~~~~~~~~~\n        3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 9: Remove ' = UNKNOWN':\n        @@ -9 +9\n        -   val value3: TestEnum? = UNKNOWN,\n        +   val value3: TestEnum?,\n        Autofix for src/slack/model/Example.kt line 10: Remove ' = UNKNOWN':\n        @@ -10 +10\n        -   val value4: TestEnum = UNKNOWN,\n        +   val value4: TestEnum,\n        Autofix for src/slack/model/Example.kt line 11: Remove ' = TestEnum.UNKNOWN':\n        @@ -11 +11\n        -   val value5: TestEnum = TestEnum.UNKNOWN,\n        +   val value5: TestEnum,\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun enum_prop_already_moshi() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          data class Example(val value: TestEnum)\n\n          @JsonClass(generateAdapter = false)\n          enum class TestEnum {\n            UNKNOWN, VALUE\n          }\n        \"\"\"\n        )\n        .indented()\n\n    lint().files(*testFiles(), source).run().expectClean()\n  }\n\n  @Test\n  fun objects_cannot_jsonClass() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          object Example\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:5: Error: Object types cannot be annotated with @JsonClass. [MoshiUsageObject]\n        @JsonClass(generateAdapter = true)\n        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 5: Remove '@JsonClass(generateAdapter = true)':\n        @@ -5 +5\n        - @JsonClass(generateAdapter = true)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun prefer_data_classes() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          class Example(val value: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:6: Error: Model classes should be immutable data classes. [MoshiUsageUseData]\n        class Example(val value: String)\n              ~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun propertyTypes() {\n    val externalType =\n      kotlin(\n          \"\"\"\n      package external\n\n      class ExternalType\n    \"\"\"\n        )\n        .indented()\n    val externalTypeAnnotated =\n      kotlin(\n          \"\"\"\n      package external\n\n      import com.squareup.moshi.JsonClass\n\n      @JsonClass(generateAdapter = true)\n      data class ExternalTypeAnnotated(val value: String)\n    \"\"\"\n        )\n        .indented()\n    val internalType =\n      kotlin(\n          \"\"\"\n      package slack\n\n      class InternalType\n    \"\"\"\n        )\n        .indented()\n    val internalTypeAnnotated =\n      kotlin(\n          \"\"\"\n      package slack\n\n      import androidx.annotation.Keep\n      import com.squareup.moshi.JsonClass\n      import com.squareup.moshi.JsonAdapter\n      import dev.zacsweers.moshix.adapters.AdaptedBy\n\n      @JsonClass(generateAdapter = true)\n      data class InternalTypeAnnotated(val value: String)\n\n      @AdaptedBy(InternalTypeAdapter::class)\n      data class InternalTypeAnnotated2(val value: String)\n\n      @Keep\n      abstract class InternalTypeAdapter : JsonAdapter.Factory\n    \"\"\"\n        )\n        .indented()\n    val jsonQualifier =\n      kotlin(\n          \"\"\"\n      package com.squareup.moshi\n\n      annotation class JsonQualifier\n    \"\"\"\n        )\n        .indented()\n    val customQualifier =\n      kotlin(\n          \"\"\"\n      package test\n\n      import com.squareup.moshi.JsonQualifier\n\n      @JsonQualifier\n      annotation class CustomQualifier\n    \"\"\"\n        )\n        .indented()\n\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import androidx.annotation.Keep\n          import com.squareup.moshi.JsonAdapter\n          import com.squareup.moshi.JsonClass\n          import java.util.ArrayList\n          import java.util.HashSet\n          import java.util.HashMap\n          import java.util.Date\n          import external.ExternalType\n          import external.ExternalTypeAnnotated\n          import slack.InternalType\n          import slack.InternalTypeAnnotated\n          import slack.InternalTypeAnnotated2\n          import dev.zacsweers.moshix.adapters.AdaptedBy\n          import test.CustomQualifier\n\n          @JsonClass(generateAdapter = true)\n          data class Example(\n            // collections\n            val okList: List<Int>,\n            val okSet: Set<Int>,\n            val okCollection: Collection<Int>,\n            val okMap: Map<String, String>,\n            val concreteList: ArrayList<Int>,\n            val concreteSet: HashSet<Int>,\n            val concreteMap: HashMap<String, String>,\n            // platform\n            val platformType: Date,\n            @AdaptedBy(DateFactory::class) val adaptedByOk: Date,\n            // external\n            val externalType: ExternalType,\n            val externalTypeAnnotated: ExternalTypeAnnotated,\n            // internal\n            val internalType: InternalType,\n            val internalTypeAnnotated: InternalTypeAnnotated,\n            val internalTypeAnnotated2: InternalTypeAnnotated2,\n            val int: Int,\n            val string: String,\n            val nullableString: String?,\n            val any: Any,\n            // Arrays\n            val arrayType: Array<String>,\n            val intArray: IntArray,\n            val boolArray: BooleanArray,\n            val complexArray: Array<List<String>>,\n            val badGeneric: List<ExternalType>,\n            val badGeneric2: CustomGenericType<ExternalType>,\n            val badNestedGeneric: CustomGenericType<List<ExternalType>>,\n            // This would normally error but since it has a custom qualifier we skip the check\n            @CustomQualifier val customQualifier: Date,\n            // Mutable collections\n            val mutableList: MutableList<Int>,\n            val mutableSet: MutableSet<Int>,\n            val mutableCollection: MutableCollection<Int>,\n            val mutableMap: MutableMap<String, String>\n          )\n\n          @Keep\n          abstract class DateFactory : JsonAdapter.Factory\n\n          @JsonClass(generateAdapter = true)\n          data class CustomGenericType<T>(val value: T)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(\n        *testFiles(),\n        externalType,\n        externalTypeAnnotated,\n        internalType,\n        internalTypeAnnotated,\n        jsonQualifier,\n        customQualifier,\n        source,\n      )\n      .allowClassNameClashes(true)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/Example.kt:43: Warning: Prefer List over Array. [MoshiUsageArray]\n          val arrayType: Array<String>,\n                         ~~~~~~~~~~~~~\n        src/slack/model/Example.kt:44: Warning: Prefer List over Array. [MoshiUsageArray]\n          val intArray: IntArray,\n                        ~~~~~~~~\n        src/slack/model/Example.kt:45: Warning: Prefer List over Array. [MoshiUsageArray]\n          val boolArray: BooleanArray,\n                         ~~~~~~~~~~~~\n        src/slack/model/Example.kt:46: Warning: Prefer List over Array. [MoshiUsageArray]\n          val complexArray: Array<List<String>>,\n                            ~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:53: Error: Use immutable collections rather than mutable versions. [MoshiUsageMutableCollections]\n          val mutableList: MutableList<Int>,\n                           ~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:54: Error: Use immutable collections rather than mutable versions. [MoshiUsageMutableCollections]\n          val mutableSet: MutableSet<Int>,\n                          ~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:55: Error: Use immutable collections rather than mutable versions. [MoshiUsageMutableCollections]\n          val mutableCollection: MutableCollection<Int>,\n                                 ~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:56: Error: Use immutable collections rather than mutable versions. [MoshiUsageMutableCollections]\n          val mutableMap: MutableMap<String, String>\n                          ~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:25: Hint: Concrete Collection type 'ArrayList' is not natively supported by Moshi. [MoshiUsageNonMoshiClassCollection]\n          val concreteList: ArrayList<Int>,\n                            ~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:26: Hint: Concrete Collection type 'HashSet' is not natively supported by Moshi. [MoshiUsageNonMoshiClassCollection]\n          val concreteSet: HashSet<Int>,\n                           ~~~~~~~~~~~~\n        src/slack/model/Example.kt:32: Error: External type 'ExternalType' is not natively supported by Moshi. [MoshiUsageNonMoshiClassExternal]\n          val externalType: ExternalType,\n                            ~~~~~~~~~~~~\n        src/slack/model/Example.kt:47: Error: External type 'ExternalType' is not natively supported by Moshi. [MoshiUsageNonMoshiClassExternal]\n          val badGeneric: List<ExternalType>,\n                          ~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:48: Error: External type 'ExternalType' is not natively supported by Moshi. [MoshiUsageNonMoshiClassExternal]\n          val badGeneric2: CustomGenericType<ExternalType>,\n                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:49: Error: External type 'ExternalType' is not natively supported by Moshi. [MoshiUsageNonMoshiClassExternal]\n          val badNestedGeneric: CustomGenericType<List<ExternalType>>,\n                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:35: Hint: Non-Moshi internal type 'InternalType' is not natively supported by Moshi. [MoshiUsageNonMoshiClassInternal]\n          val internalType: InternalType,\n                            ~~~~~~~~~~~~\n        src/slack/model/Example.kt:27: Hint: Concrete Map type 'HashMap' is not natively supported by Moshi. [MoshiUsageNonMoshiClassMap]\n          val concreteMap: HashMap<String, String>,\n                           ~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/Example.kt:29: Warning: Platform type 'Date' is not natively supported by Moshi. [MoshiUsageNonMoshiClassPlatform]\n          val platformType: Date,\n                            ~~~~\n        8 errors, 5 warnings, 4 hints\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 43: Change to List<String>:\n        @@ -43 +43\n        -   val arrayType: Array<String>,\n        +   val arrayType: List<String>,\n        Autofix for src/slack/model/Example.kt line 44: Change to List<Int>:\n        @@ -44 +44\n        -   val intArray: IntArray,\n        +   val intArray: List<Int>,\n        Autofix for src/slack/model/Example.kt line 45: Change to List<Boolean>:\n        @@ -45 +45\n        -   val boolArray: BooleanArray,\n        +   val boolArray: List<Boolean>,\n        Autofix for src/slack/model/Example.kt line 46: Change to List<List<String>>:\n        @@ -46 +46\n        -   val complexArray: Array<List<String>>,\n        +   val complexArray: List<List<String>>,\n        Autofix for src/slack/model/Example.kt line 53: Change to List:\n        @@ -53 +53\n        -   val mutableList: MutableList<Int>,\n        +   val mutableList: List<Int>,\n        Autofix for src/slack/model/Example.kt line 54: Change to Set:\n        @@ -54 +54\n        -   val mutableSet: MutableSet<Int>,\n        +   val mutableSet: Set<Int>,\n        Autofix for src/slack/model/Example.kt line 55: Change to Collection:\n        @@ -55 +55\n        -   val mutableCollection: MutableCollection<Int>,\n        +   val mutableCollection: Collection<Int>,\n        Autofix for src/slack/model/Example.kt line 56: Change to Map:\n        @@ -56 +56\n        -   val mutableMap: MutableMap<String, String>\n        +   val mutableMap: Map<String, String>\n        Autofix for src/slack/model/Example.kt line 25: Change to List:\n        @@ -25 +25\n        -   val concreteList: ArrayList<Int>,\n        +   val concreteList: List<Int>,\n        Autofix for src/slack/model/Example.kt line 26: Change to Set:\n        @@ -26 +26\n        -   val concreteSet: HashSet<Int>,\n        +   val concreteSet: Set<Int>,\n        Autofix for src/slack/model/Example.kt line 27: Change to Map:\n        @@ -27 +27\n        -   val concreteMap: HashMap<String, String>,\n        +   val concreteMap: Map<String, String>,\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_jsonQualifierAnnotation_ok() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.JsonQualifier\n          import kotlin.annotation.Retention\n          import kotlin.annotation.AnnotationRetention\n          import kotlin.annotation.AnnotationRetention.RUNTIME\n          import kotlin.annotation.AnnotationTarget.PROPERTY\n          import kotlin.annotation.AnnotationTarget\n          import kotlin.annotation.AnnotationTarget.FIELD\n\n          @JsonQualifier\n          annotation class NoAnnotationsIsOk\n\n          @Target(FIELD)\n          @JsonQualifier\n          annotation class NoRetentionIsOk\n\n          @Target(AnnotationRetention.FIELD)\n          @Retention(AnnotationRetention.RUNTIME)\n          @JsonQualifier\n          annotation class CorrectTargetAndRetention\n\n          @Target(PROPERTY, AnnotationRetention.FIELD)\n          @Retention(RUNTIME)\n          @JsonQualifier\n          annotation class CorrectTargetAndRetention2\n\n          @Target([PROPERTY, FIELD])\n          @Retention(RUNTIME)\n          @JsonQualifier\n          annotation class CorrectTargetAndRetention3\n\n          @Target(PROPERTY)\n          @JsonQualifier\n          annotation class MissingTarget\n\n          @Retention(AnnotationRetention.BINARY)\n          @JsonQualifier\n          annotation class WrongRetention\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/model/NoAnnotationsIsOk.kt:37: Error: JsonQualifiers must have RUNTIME retention. [MoshiUsageQualifierRetention]\n        @Retention(AnnotationRetention.BINARY)\n        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/slack/model/NoAnnotationsIsOk.kt:33: Error: JsonQualifiers must include FIELD targeting. [MoshiUsageQualifierTarget]\n        @Target(PROPERTY)\n        ~~~~~~~~~~~~~~~~~\n        2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/NoAnnotationsIsOk.kt line 37: Remove '@Retention(AnnotationRetention.BINARY)':\n        @@ -37 +37\n        - @Retention(AnnotationRetention.BINARY)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java_jsonQualifierAnnotation_ok() {\n    val source =\n      java(\n          \"\"\"\n          package slack.model;\n\n          import com.squareup.moshi.JsonQualifier;\n          import java.lang.annotation.ElementType;\n          import static java.lang.annotation.ElementType.FIELD;\n          import java.lang.annotation.Retention;\n          import java.lang.annotation.RetentionPolicy;\n          import static java.lang.annotation.RetentionPolicy.RUNTIME;\n          import java.lang.annotation.Target;\n\n          @Retention(RetentionPolicy.RUNTIME)\n          @JsonQualifier\n          public @interface NoTargetIsOk {}\n\n          @Target(ElementType.FIELD)\n          @Retention(RetentionPolicy.RUNTIME)\n          @JsonQualifier\n          public @interface CorrectTargetAndRetention {}\n\n          @Target({FIELD, ElementType.METHOD})\n          @Retention(RUNTIME)\n          @JsonQualifier\n          public @interface CorrectTargetAndRetention2 {}\n\n          @Target(ElementType.METHOD)\n          @Retention(RUNTIME)\n          @JsonQualifier\n          public @interface MissingField {}\n\n          @Target(FIELD)\n          @Retention(RetentionPolicy.CLASS)\n          @JsonQualifier\n          public @interface WrongRetention {}\n\n          @Target(FIELD)\n          @JsonQualifier\n          public @interface MissingRetention {}\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/NoTargetIsOk.java:31: Error: JsonQualifiers must have RUNTIME retention. [MoshiUsageQualifierRetention]\n          @Retention(RetentionPolicy.CLASS)\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/model/NoTargetIsOk.java:37: Error: JsonQualifiers must have RUNTIME retention. [MoshiUsageQualifierRetention]\n          public @interface MissingRetention {}\n                            ~~~~~~~~~~~~~~~~\n          src/slack/model/NoTargetIsOk.java:25: Error: JsonQualifiers must include FIELD targeting. [MoshiUsageQualifierTarget]\n          @Target(ElementType.METHOD)\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/NoTargetIsOk.java line 31: Replace with RUNTIME:\n        @@ -31 +31\n        - @Retention(RetentionPolicy.CLASS)\n        + @Retention(RetentionPolicy.RUNTIME)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun redundantJsonName() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.Json\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          data class Example(@Json(name = \"value\") val value: String)\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:7: Warning: Json.name with the same value as the property/enum member name is redundant. [MoshiUsageRedundantJsonName]\n          data class Example(@Json(name = \"value\") val value: String)\n                                          ~~~~~~~\n          0 errors, 1 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 7: Remove '@Json(name = \"value\")':\n        @@ -7 +7\n        - data class Example(@Json(name = \"value\") val value: String)\n        @@ -8 +7\n        + data class Example( val value: String)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun serializedNameIssues() {\n    val serializedName =\n      java(\n        \"\"\"\n          package com.google.gson.annotations;\n\n          public @interface SerializedName {\n            String value();\n            String[] alternate() default {};\n          }\n        \"\"\"\n          .trimIndent()\n      )\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.Json\n          import com.squareup.moshi.JsonClass\n          import com.google.gson.annotations.SerializedName\n\n          @JsonClass(generateAdapter = true)\n          data class Example(\n            val noAnnotations: String,\n            @Json(name = \"full_moshi\") val fullMoshi: String,\n            @SerializedName(\"full_gson\") val fullGson: String,\n            @SerializedName(\"full_gson_alts\", alternate = [\"foo\"]) val fullGsonAlternates: String,\n            @Json(name = \"mixed\") @SerializedName(\"mixed\") val mixedSame: String,\n            @Json(name = \"mixed_diff\") @SerializedName(\"mixed_diff_2\") val mixedDiff: String,\n            @Json(name = \"mixed_alts\") @SerializedName(\"mixed_alts\", alternate = [\"foo\"]) val mixedAlternates: String,\n          )\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), serializedName, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:11: Error: Use Moshi's @Json rather than Gson's @SerializedName. [MoshiUsageSerializedName]\n            @SerializedName(\"full_gson\") val fullGson: String,\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/model/Example.kt:12: Error: Use Moshi's @Json rather than Gson's @SerializedName. [MoshiUsageSerializedName]\n            @SerializedName(\"full_gson_alts\", alternate = [\"foo\"]) val fullGsonAlternates: String,\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/model/Example.kt:13: Error: Use Moshi's @Json rather than Gson's @SerializedName. [MoshiUsageSerializedName]\n            @Json(name = \"mixed\") @SerializedName(\"mixed\") val mixedSame: String,\n                                  ~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/model/Example.kt:14: Error: Use Moshi's @Json rather than Gson's @SerializedName. [MoshiUsageSerializedName]\n            @Json(name = \"mixed_diff\") @SerializedName(\"mixed_diff_2\") val mixedDiff: String,\n                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/model/Example.kt:15: Error: Use Moshi's @Json rather than Gson's @SerializedName. [MoshiUsageSerializedName]\n            @Json(name = \"mixed_alts\") @SerializedName(\"mixed_alts\", alternate = [\"foo\"]) val mixedAlternates: String,\n                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/model/Example.kt line 11: Replace with @Json(name = \"full_gson\"):\n        @@ -11 +11\n        -   @SerializedName(\"full_gson\") val fullGson: String,\n        +   @Json(name = \"full_gson\") val fullGson: String,\n        Autofix for src/slack/model/Example.kt line 13: Remove '@SerializedName(\"mixed\")':\n        @@ -13 +13\n        -   @Json(name = \"mixed\") @SerializedName(\"mixed\") val mixedSame: String,\n        +   @Json(name = \"mixed\")  val mixedSame: String,\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun duplicateNames() {\n    val source =\n      kotlin(\n          \"\"\"\n          package slack.model\n\n          import com.squareup.moshi.Json\n          import com.squareup.moshi.JsonClass\n\n          @JsonClass(generateAdapter = true)\n          data class Example(\n            val value: String,\n            @Json(name = \"value\") val anotherValue: String,\n            @Json(name = \"value2\") val anotherValue2: String,\n            @Json(name = \"value2\") val anotherValue3: String\n          )\n        \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*testFiles(), source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/model/Example.kt:8: Error: Name 'value' is duplicated by member 'anotherValue'. [MoshiUsageDuplicateJsonName]\n            val value: String,\n                ~~~~~\n          src/slack/model/Example.kt:9: Error: Name 'value' is duplicated by member 'value'. [MoshiUsageDuplicateJsonName]\n            @Json(name = \"value\") val anotherValue: String,\n                                      ~~~~~~~~~~~~\n          src/slack/model/Example.kt:10: Error: Name 'value2' is duplicated by member 'anotherValue3'. [MoshiUsageDuplicateJsonName]\n            @Json(name = \"value2\") val anotherValue2: String,\n                                       ~~~~~~~~~~~~~\n          src/slack/model/Example.kt:11: Error: Name 'value2' is duplicated by member 'anotherValue2'. [MoshiUsageDuplicateJsonName]\n            @Json(name = \"value2\") val anotherValue3: String\n                                       ~~~~~~~~~~~~~\n          4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  private fun testFiles() =\n    arrayOf(\n      keepAnnotation,\n      jsonClassAnnotation,\n      jsonAnnotation,\n      jsonQualifierAnnotation,\n      typeLabel,\n      defaultObject,\n      adaptedBy,\n      jsonAdapter,\n    )\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/MustUseNamedParamsDetectorTest.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport org.junit.Test\n\nclass MustUseNamedParamsDetectorTest : BaseSlackLintTest() {\n  private companion object {\n    private val mustUseNamedParams =\n      kotlin(\n          \"\"\"\n      package slack.lint.annotations\n\n      /**\n       * Callers to this function must named all parameters.\n       */\n      @Target(AnnotationTarget.FUNCTION)\n      @Retention(AnnotationRetention.RUNTIME)\n      annotation class MustUseNamedParams\n      \"\"\"\n        )\n        .indented()\n  }\n\n  override fun getDetector(): Detector = MustUseNamedParamsDetector()\n\n  override fun getIssues(): List<Issue> = listOf(MustUseNamedParamsDetector.ISSUE)\n\n  @Test\n  fun simpleTest() {\n    lint()\n      .files(\n        mustUseNamedParams,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import slack.lint.annotations.MustUseNamedParams\n\n          class TestFile {\n            @MustUseNamedParams\n            fun methodWithAnnotation(name: String) {\n              // Do nothing.\n            }\n\n            fun methodWithoutAnnotation(name: String) {\n              // Do nothing.\n            }\n\n            fun useMethod() {\n              methodWithAnnotation(\"Zac\")\n              methodWithAnnotation(name = \"Sean\")\n\n              methodWithoutAnnotation(\"Yifan\")\n              methodWithoutAnnotation(name = \"Sean2\")\n            }\n          }\n        \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/TestFile.kt:16: Error: Calls to @MustUseNamedParams-annotated methods must name all parameters. [MustUseNamedParams]\n              methodWithAnnotation(\"Zac\")\n              ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/NonKotlinPairDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.detector.api.Detector\nimport org.junit.Test\n\nclass NonKotlinPairDetectorTest : BaseSlackLintTest() {\n  override fun getDetector(): Detector = NonKotlinPairDetector()\n\n  override fun getIssues() = NonKotlinPairDetector.issues.toList()\n\n  @Test\n  fun `Java - AndroidX Pair create shows warning`() {\n    lint()\n      .files(\n        ANDROIDX_PAIR_STUB,\n        java(\n            \"\"\"\n                  package slack.test;\n\n                  import androidx.core.util.Pair;\n\n                  public class TestClass {\n\n                    public void doStuff() {\n                      new Integer(5);\n                      new String(\"TestString\").toString();\n                      Pair pair = Pair.create(\"first\", \"second\");\n                      pair.first.toString();\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*NonKotlinPairDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n              src/slack/test/TestClass.java:10: Warning: Use Kotlin's kotlin.Pair instead of other Pair types from other libraries like AndroidX and Slack commons [KotlinPairNotCreated]\n                  Pair pair = Pair.create(\"first\", \"second\");\n                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n              0 errors, 1 warnings\n            \"\"\"\n      )\n  }\n\n  @Test\n  fun `Java - AndroidX Pair constructor create shows warning`() {\n    lint()\n      .files(\n        ANDROIDX_PAIR_STUB,\n        java(\n            \"\"\"\n                  package slack.test;\n\n                  import androidx.core.util.Pair;\n\n                  public class TestClass {\n\n                    public void doStuff() {\n                      new Integer(5);\n                      new String(\"TestString\").toString();\n                      new Pair<>(\"first\", \"second\").first.toString();\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*NonKotlinPairDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n              src/slack/test/TestClass.java:10: Warning: Use Kotlin's kotlin.Pair instead of other Pair types from other libraries like AndroidX and Slack commons [KotlinPairNotCreated]\n                  new Pair<>(\"first\", \"second\").first.toString();\n                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n              0 errors, 1 warnings\n            \"\"\"\n      )\n  }\n\n  @Test\n  fun `Java - Slack commons Pair constructor create shows warning`() {\n    lint()\n      .files(\n        SLACK_COMMONS_PAIR,\n        java(\n            \"\"\"\n                  package slack.test;\n\n                  import slack.commons.Pair;\n\n                  public class TestClass {\n\n                    public void doStuff() {\n                      new Integer(5);\n                      new String(\"TestString\").toString();\n                      new Pair<>(\"first\", \"second\").getFirst();\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*NonKotlinPairDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n              src/slack/test/TestClass.java:10: Warning: Use Kotlin's kotlin.Pair instead of other Pair types from other libraries like AndroidX and Slack commons [KotlinPairNotCreated]\n                  new Pair<>(\"first\", \"second\").getFirst();\n                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n              0 errors, 1 warnings\n            \"\"\"\n      )\n  }\n\n  @Test\n  fun `Java - Full qualified Slack commons Pair constructor create shows warning`() {\n    lint()\n      .files(\n        SLACK_COMMONS_PAIR,\n        java(\n            \"\"\"\n                  package slack.test;\n\n                  public class TestClass {\n\n                    public void doStuff() {\n                      new Integer(5);\n                      new String(\"TestString\").toString();\n                      new slack.commons.Pair<>(\"first\", \"second\").getFirst();\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*NonKotlinPairDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n              src/slack/test/TestClass.java:8: Warning: Use Kotlin's kotlin.Pair instead of other Pair types from other libraries like AndroidX and Slack commons [KotlinPairNotCreated]\n                  new slack.commons.Pair<>(\"first\", \"second\").getFirst();\n                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n              0 errors, 1 warnings\n            \"\"\"\n      )\n  }\n\n  @Test\n  fun `Kotlin - Kotlin Pair constructor create has no warnings`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n                  package slack.test\n\n                  class TestClass {\n\n                    fun doStuff() {\n                      String(\"TestString\").toString()\n                      Integer(6)\n                      val pair = Pair(\"first\", \"second\")\n                    }\n                  }\n                \"\"\"\n          )\n          .indented()\n      )\n      .issues(*NonKotlinPairDetector.issues.toTypedArray())\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `Kotlin - AndroidX Pair create shows warning`() {\n    lint()\n      .files(\n        ANDROIDX_PAIR_STUB,\n        kotlin(\n            \"\"\"\n                  package slack.test\n\n                  import androidx.core.util.Pair\n\n                  class TestClass {\n\n                    fun doStuff() {\n                      Integer(5)\n                      String(\"TestString\").toString()\n                      val pair = Pair.create(\"first\", \"second\")\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*NonKotlinPairDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n              src/slack/test/TestClass.kt:10: Warning: Use Kotlin's kotlin.Pair instead of other Pair types from other libraries like AndroidX and Slack commons [KotlinPairNotCreated]\n                  val pair = Pair.create(\"first\", \"second\")\n                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n              0 errors, 1 warnings\n            \"\"\"\n      )\n  }\n\n  @Test\n  fun `Kotlin - AndroidX Pair constructor create shows warning`() {\n    lint()\n      .files(\n        ANDROIDX_PAIR_STUB,\n        kotlin(\n            \"\"\"\n                  package slack.test\n\n                  import androidx.core.util.Pair\n\n                  class TestClass {\n\n                    fun doStuff() {\n                      Integer(5)\n                      String(\"TestString\").toString()\n                      val pair = Pair(\"first\", \"second\")\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*NonKotlinPairDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n              src/slack/test/TestClass.kt:10: Warning: Use Kotlin's kotlin.Pair instead of other Pair types from other libraries like AndroidX and Slack commons [KotlinPairNotCreated]\n                  val pair = Pair(\"first\", \"second\")\n                             ~~~~~~~~~~~~~~~~~~~~~~~\n              0 errors, 1 warnings\n            \"\"\"\n      )\n  }\n\n  @Test\n  fun `Kotlin - Slack commons Pair constructor create shows warning`() {\n    lint()\n      .files(\n        SLACK_COMMONS_PAIR,\n        kotlin(\n            \"\"\"\n                  package slack.test\n\n                  import slack.commons.Pair\n\n                  class TestClass {\n\n                    fun doStuff() {\n                      Integer(5)\n                      Integer(6)\n                      String(\"TestString\").toString()\n                      val pair = Pair(\"first\", \"second\")\n                    }\n                  }\n                \"\"\"\n          )\n          .indented(),\n      )\n      .issues(*NonKotlinPairDetector.issues.toTypedArray())\n      .run()\n      .expect(\n        \"\"\"\n              src/slack/test/TestClass.kt:11: Warning: Use Kotlin's kotlin.Pair instead of other Pair types from other libraries like AndroidX and Slack commons [KotlinPairNotCreated]\n                  val pair = Pair(\"first\", \"second\")\n                             ~~~~~~~~~~~~~~~~~~~~~~~\n              0 errors, 1 warnings\n            \"\"\"\n      )\n  }\n\n  companion object {\n    private val ANDROIDX_PAIR_STUB =\n      java(\n        \"\"\"\n          package androidx.core.util;\n          public class Pair<F, S> {\n            public final F first;\n            public final S second;\n\n            public Pair(F first, S second) {\n                this.first = first;\n                this.second = second;\n            }\n\n            public static <A, B> Pair<A, B> create(A a, B b) {\n                return null;\n            }\n          }\n        \"\"\"\n      )\n\n    private val SLACK_COMMONS_PAIR =\n      kotlin(\n        \"\"\"\n          package slack.commons\n          data class Pair<out A, out B>(\n              val first: A,\n              val second: B\n          )\n        \"\"\"\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/NotNullOperatorDetectorTest.kt",
    "content": "// Copyright (C) 2024 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport kotlin.text.trimIndent\nimport org.junit.Test\n\nclass NotNullOperatorDetectorTest : BaseSlackLintTest() {\n  override fun getDetector() = NotNullOperatorDetector()\n\n  override fun getIssues() = listOf(NotNullOperatorDetector.ISSUE)\n\n  @Test\n  fun testDocumentationExample() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n            package foo\n\n            class Test {\n              fun doNothing(t: String?): Boolean {\n                t/* this is legal */!!.length\n                return t!!.length == 1\n              }\n            }\n          \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n          src/foo/Test.kt:6: Warning: Avoid using the !! operator [AvoidUsingNotNullOperator]\n                          t/* this is legal */!!.length\n                                              ~~\n          src/foo/Test.kt:7: Warning: Avoid using the !! operator [AvoidUsingNotNullOperator]\n                          return t!!.length == 1\n                                  ~~\n          0 errors, 2 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test clean`() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n            package foo\n\n            class Test {\n              fun doNothing(t: String?): Boolean {\n                // Should not match despite the !! string in it\n                \"!!\".length++\n                return t?.length == 1\n              }\n            }\n          \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/NullableConcurrentHashMapDetectorTest.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport org.junit.Test\n\nclass NullableConcurrentHashMapDetectorTest : BaseSlackLintTest() {\n  override fun getDetector() = NullableConcurrentHashMapDetector()\n\n  override fun getIssues() = listOf(NullableConcurrentHashMapDetector.ISSUE)\n\n  @Test\n  fun concurrentHashMapWithNonNullableTypes() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n           fun test() {\n               val map = java.util.concurrent.ConcurrentHashMap<String, Int>()\n               val map2: ConcurrentHashMap<String, Int> = java.util.concurrent.ConcurrentHashMap()\n           }\n           \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun concurrentHashMapWithNullableKeyType() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n           fun test() {\n               val map = java.util.concurrent.ConcurrentHashMap<String?, Int>()\n               val map2: java.util.concurrent.ConcurrentHashMap<String?, Int> = java.util.concurrent.ConcurrentHashMap()\n           }\n           \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test.kt:3: Error: ConcurrentHashMap should not use nullable key types [NullableConcurrentHashMap]\n                       val map = java.util.concurrent.ConcurrentHashMap<String?, Int>()\n                                                                        ~~~~~~~\n        src/test.kt:4: Error: ConcurrentHashMap should not use nullable key types [NullableConcurrentHashMap]\n                       val map2: java.util.concurrent.ConcurrentHashMap<String?, Int> = java.util.concurrent.ConcurrentHashMap()\n                                                                        ~~~~~~~\n        2 errors\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun concurrentHashMapWithNullableValueType() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          fun test() {\n              val map = java.util.concurrent.ConcurrentHashMap<String, Int?>()\n          }\n          \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test.kt:3: Error: ConcurrentHashMap should not use nullable value types [NullableConcurrentHashMap]\n                      val map = java.util.concurrent.ConcurrentHashMap<String, Int?>()\n                                                                               ~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun concurrentHashMapWithBothNullableTypes() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          fun test() {\n              val map = java.util.concurrent.ConcurrentHashMap<String?, Int?>()\n          }\n          \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test.kt:3: Error: ConcurrentHashMap should not use nullable key types [NullableConcurrentHashMap]\n                      val map = java.util.concurrent.ConcurrentHashMap<String?, Int?>()\n                                                                       ~~~~~~~\n        src/test.kt:3: Error: ConcurrentHashMap should not use nullable value types [NullableConcurrentHashMap]\n                      val map = java.util.concurrent.ConcurrentHashMap<String?, Int?>()\n                                                                                ~~~~\n        2 errors\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaConcurrentHashMapWithNullableAnnotation() {\n    lint()\n      .files(\n        java(\n          \"\"\"\n          package test;\n\n          import java.lang.annotation.ElementType;\n          import java.lang.annotation.Target;\n          import java.util.concurrent.ConcurrentHashMap;\n\n          public class Test {\n              @Target(ElementType.TYPE)\n              public @interface Nullable {}\n\n              public void test() {\n                  var map = new ConcurrentHashMap<@Nullable String, Integer>();\n                  ConcurrentHashMap<@Nullable String, Integer> map2 = new ConcurrentHashMap<>();\n              }\n          }\n          \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/Test.java:13: Error: ConcurrentHashMap should not use nullable key types [NullableConcurrentHashMap]\n                          var map = new ConcurrentHashMap<@Nullable String, Integer>();\n                                                          ~~~~~~~~~~~~~~~~\n        src/test/Test.java:14: Error: ConcurrentHashMap should not use nullable key types [NullableConcurrentHashMap]\n                          ConcurrentHashMap<@Nullable String, Integer> map2 = new ConcurrentHashMap<>();\n                                            ~~~~~~~~~~~~~~~~\n        2 errors\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun concurrentHashMapDeclaredSeparately() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          class TestClass {\n              private val map: java.util.concurrent.ConcurrentHashMap<String, Int>\n\n              init {\n                  map = java.util.concurrent.ConcurrentHashMap()\n              }\n          }\n          \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun concurrentHashMapDeclaredSeparatelyWithNullableTypes() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          class TestClass {\n              private val map: java.util.concurrent.ConcurrentHashMap<String?, Int?>\n\n              init {\n                  map = java.util.concurrent.ConcurrentHashMap()\n              }\n          }\n          \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/TestClass.kt:3: Error: ConcurrentHashMap should not use nullable key types [NullableConcurrentHashMap]\n                      private val map: java.util.concurrent.ConcurrentHashMap<String?, Int?>\n                                                                              ~~~~~~~\n        src/TestClass.kt:3: Error: ConcurrentHashMap should not use nullable value types [NullableConcurrentHashMap]\n                      private val map: java.util.concurrent.ConcurrentHashMap<String?, Int?>\n                                                                                       ~~~~\n        2 errors\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/RawDispatchersUsageDetectorTest.kt",
    "content": "// Copyright (C) 2020 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport org.junit.Test\n\nclass RawDispatchersUsageDetectorTest : BaseSlackLintTest() {\n\n  companion object {\n    // Stub of dispatchers.\n    // Use a combination of string constants and getters to ensure coverage for both\n    // Also add an extension function to check we don't try to lint those.\n    // language=kotlin\n    private val DISPATCHERS_STUB =\n      kotlin(\n        \"\"\"\n        package kotlinx.coroutines\n\n        object Dispatchers {\n            @JvmStatic\n            val Default: String get() = error()\n\n            @JvmStatic\n            val Main: String get() = error()\n\n            @JvmStatic\n            val Unconfined: String = \"\"\n\n            @JvmStatic\n            val IO: String = \"\"\n        }\n\n        fun Dispatchers.someExtension() {\n\n        }\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  override fun getDetector() = RawDispatchersUsageDetector()\n\n  override fun getIssues() = listOf(RawDispatchersUsageDetector.ISSUE)\n\n  @Test\n  fun simple() {\n    lint()\n      .files(\n        DISPATCHERS_STUB,\n        kotlin(\n            \"\"\"\n              package test.pkg\n\n              import kotlinx.coroutines.Dispatchers\n\n              fun example() {\n                Dispatchers.IO\n                Dispatchers.Default\n                Dispatchers.Unconfined\n                Dispatchers.Main\n                Dispatchers.someExtension()\n                Dispatchers::IO\n                Dispatchers::Default\n                Dispatchers::Unconfined\n                Dispatchers::Main\n              }\n            \"\"\"\n              .trimIndent()\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n          src/test/pkg/test.kt:6: Error: Use SlackDispatchers. [RawDispatchersUse]\n            Dispatchers.IO\n            ~~~~~~~~~~~~~~\n          src/test/pkg/test.kt:7: Error: Use SlackDispatchers. [RawDispatchersUse]\n            Dispatchers.Default\n            ~~~~~~~~~~~~~~~~~~~\n          src/test/pkg/test.kt:8: Error: Use SlackDispatchers. [RawDispatchersUse]\n            Dispatchers.Unconfined\n            ~~~~~~~~~~~~~~~~~~~~~~\n          src/test/pkg/test.kt:9: Error: Use SlackDispatchers. [RawDispatchersUse]\n            Dispatchers.Main\n            ~~~~~~~~~~~~~~~~\n          src/test/pkg/test.kt:11: Error: Use SlackDispatchers. [RawDispatchersUse]\n            Dispatchers::IO\n            ~~~~~~~~~~~~~~~\n          src/test/pkg/test.kt:12: Error: Use SlackDispatchers. [RawDispatchersUse]\n            Dispatchers::Default\n            ~~~~~~~~~~~~~~~~~~~~\n          src/test/pkg/test.kt:13: Error: Use SlackDispatchers. [RawDispatchersUse]\n            Dispatchers::Unconfined\n            ~~~~~~~~~~~~~~~~~~~~~~~\n          src/test/pkg/test.kt:14: Error: Use SlackDispatchers. [RawDispatchersUse]\n            Dispatchers::Main\n            ~~~~~~~~~~~~~~~~~\n          8 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  // Usage in tests are fine\n  @Test\n  fun testsAreFine() {\n    lint()\n      .files(\n        DISPATCHERS_STUB,\n        kotlin(\n            \"test/test/pkg/Test.kt\",\n            \"\"\"\n              package test.pkg\n\n              import kotlinx.coroutines.Dispatchers\n\n              fun example() {\n                Dispatchers.IO\n                Dispatchers.Default\n                Dispatchers.Unconfined\n                Dispatchers.Main\n                Dispatchers.someExtension()\n                Dispatchers::IO\n                Dispatchers::Default\n                Dispatchers::Unconfined\n                Dispatchers::Main\n              }\n            \"\"\"\n              .trimIndent(),\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/RedactedUsageDetectorTest.kt",
    "content": "// Copyright (C) 2020 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport org.junit.Test\n\nclass RedactedUsageDetectorTest : BaseSlackLintTest() {\n\n  companion object {\n    private val REDACTED_STUB =\n      kotlin(\n        \"\"\"\n        package slack.annotations\n\n        annotation class Redacted\n        annotation class AnotherRedacted\n        annotation class AnotherAnnotation\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  override fun getDetector() = RedactedUsageDetector()\n\n  override fun getIssues() = RedactedUsageDetector.ISSUES.toList()\n\n  @Test\n  fun smokeTest() {\n    lint()\n      .files(\n        REDACTED_STUB,\n        kotlin(\n            \"\"\"\n            package test.pkg\n\n            import slack.annotations.Redacted\n            import slack.annotations.AnotherRedacted\n            import slack.annotations.AnotherAnnotation\n\n            @Redacted\n            data class RedactedClass(val value: String)\n\n            data class RedactedProps(@Redacted val value: String)\n          \"\"\"\n          )\n          .indented(),\n        java(\n            \"\"\"\n            package test.pkg;\n\n            import slack.annotations.Redacted;\n            import slack.annotations.AnotherAnnotation;\n\n            @Redacted\n            class RedactedClass {\n              @Redacted\n              int value;\n              @AnotherAnnotation\n              int value2;\n\n              @Redacted\n              public int getValue() {\n                return value;\n              }\n\n              @AnotherAnnotation\n              public int getValue2() {\n                return value2;\n              }\n\n              @AnotherAnnotation\n              static class AnotherInner {\n\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n        java(\n            \"\"\"\n            package test.pkg;\n\n            import slack.annotations.Redacted;\n            import slack.annotations.AnotherRedacted;\n\n            @AnotherRedacted\n            class AnotherRedactedClass {\n              @AnotherRedacted\n              int value;\n\n              @AnotherRedacted\n              public int getValue() {\n                return value;\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .allowClassNameClashes(true)\n      .run()\n      .expect(\n        \"\"\"\n          src/test/pkg/AnotherRedactedClass.java:6: Error: @Redacted is only supported in Kotlin classes! [RedactedInJavaUsage]\n          @AnotherRedacted\n          ~~~~~~~~~~~~~~~~\n          src/test/pkg/AnotherRedactedClass.java:8: Error: @Redacted is only supported in Kotlin classes! [RedactedInJavaUsage]\n            @AnotherRedacted\n            ~~~~~~~~~~~~~~~~\n          src/test/pkg/AnotherRedactedClass.java:11: Error: @Redacted is only supported in Kotlin classes! [RedactedInJavaUsage]\n            @AnotherRedacted\n            ~~~~~~~~~~~~~~~~\n          src/test/pkg/RedactedClass.java:6: Error: @Redacted is only supported in Kotlin classes! [RedactedInJavaUsage]\n          @Redacted\n          ~~~~~~~~~\n          src/test/pkg/RedactedClass.java:8: Error: @Redacted is only supported in Kotlin classes! [RedactedInJavaUsage]\n            @Redacted\n            ~~~~~~~~~\n          src/test/pkg/RedactedClass.java:13: Error: @Redacted is only supported in Kotlin classes! [RedactedInJavaUsage]\n            @Redacted\n            ~~~~~~~~~\n          6 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/RestrictCallsToDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport org.junit.Test\n\nclass RestrictCallsToDetectorTest : BaseSlackLintTest() {\n\n  private companion object {\n    private val restrictCallsTo =\n      kotlin(\n          \"\"\"\n        package slack.lint.annotations\n        import java.lang.annotation.Inherited\n\n        @Inherited\n        annotation class RestrictCallsTo(val scope: Int) {\n          companion object {\n            const val FILE = 0\n          }\n        }\n      \"\"\"\n        )\n        .indented()\n  }\n\n  override fun getDetector() = RestrictCallsToDetector()\n\n  override fun getIssues() = listOf(RestrictCallsToDetector.ISSUE)\n\n  @Test\n  fun smokeTest() {\n    lint()\n      .files(\n        restrictCallsTo,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import slack.lint.annotations.RestrictCallsTo\n          import slack.lint.annotations.RestrictCallsTo.Companion.FILE\n\n          interface MyApi {\n            fun example()\n\n            @RestrictCallsTo(FILE)\n            fun annotatedExample()\n          }\n\n          class SameFile {\n            fun doStuffWith(api: MyApi) {\n              // This is ok\n              api.example()\n              api.annotatedExample()\n            }\n          }\n\n          class MyApiImpl : MyApi {\n            override fun example() {\n              annotatedExample()\n            }\n\n            // Note this is not annotated, ensures we check up the hierarchy\n            override fun annotatedExample() {\n              println(\"Hello\")\n            }\n          }\n        \"\"\"\n          )\n          .indented(),\n        kotlin(\n            \"\"\"\n          package foo\n\n          class DifferentFile {\n            fun doStuffWith(api: MyApi) {\n              // This is ok\n              api.example()\n              // This is not\n              api.annotatedExample()\n            }\n          }\n\n          class MyApiImpl2 : MyApi {\n            override fun example() {\n              // Not ok\n              annotatedExample()\n            }\n\n            // Still ok\n            override fun annotatedExample() {\n              println(\"Hello\")\n            }\n\n            fun backdoor() {\n              // Backdoors don't work either! This isn't annotated on the impl but we check the\n              // original overridden type.\n              MyApiImpl().annotatedExample()\n            }\n          }\n        \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/DifferentFile.kt:8: Error: Methods annotated with @RestrictedCallsTo should only be called from the specified scope. [RestrictCallsTo]\n            api.annotatedExample()\n            ~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/DifferentFile.kt:15: Error: Methods annotated with @RestrictedCallsTo should only be called from the specified scope. [RestrictCallsTo]\n            annotatedExample()\n            ~~~~~~~~~~~~~~~~~~\n        src/foo/DifferentFile.kt:26: Error: Methods annotated with @RestrictedCallsTo should only be called from the specified scope. [RestrictCallsTo]\n            MyApiImpl().annotatedExample()\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/SerializableDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport org.junit.Test\n\nclass SerializableDetectorTest : BaseSlackLintTest() {\n\n  override fun getDetector() = SerializableDetector()\n\n  override fun getIssues() = listOf(SerializableDetector.ISSUE)\n\n  @Test\n  fun kotlin_happyPath() {\n    lint()\n      .detector(RawDispatchersUsageDetector())\n      .issues(RawDispatchersUsageDetector.ISSUE)\n      .files(\n        kotlin(\n            \"\"\"\n            package slack\n\n            class ImplementsNothing\n          \"\"\"\n          )\n          .indented()\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun kotlin_happyPath_implementsBoth() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            package slack\n\n            import java.io.Serializable\n            import android.os.Parcelable\n\n            class ImplementsBoth : Serializable, Parcelable\n          \"\"\"\n          )\n          .indented()\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun kotlin_implicitPlatformType_isIgnored() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            package slack\n\n            import kotlin.RuntimeException\n\n            class ImplementsImplicitly : RuntimeException\n          \"\"\"\n          )\n          .indented()\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun kotlin_explicitPlatformType_isNotIgnored() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            package slack\n\n            import java.io.Serializable\n            import kotlin.RuntimeException\n\n            class ImplementsExplicitly : RuntimeException, Serializable\n          \"\"\"\n          )\n          .indented()\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/ImplementsExplicitly.kt:6: Error: Don't use Serializable. [SerializableUsage]\n        class ImplementsExplicitly : RuntimeException, Serializable\n              ~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlin_failure() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            package slack\n\n            import java.io.Serializable\n\n            class BadClass : Serializable\n          \"\"\"\n          )\n          .indented()\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/BadClass.kt:5: Error: Don't use Serializable. [SerializableUsage]\n        class BadClass : Serializable\n              ~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n      )\n  }\n\n  @Test\n  fun kotlin_failure_kotlin_io() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n            package slack\n\n            import kotlin.io.Serializable\n\n            class BadClass : Serializable\n          \"\"\"\n          )\n          .indented()\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/BadClass.kt:5: Error: Don't use Serializable. [SerializableUsage]\n        class BadClass : Serializable\n              ~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n      )\n  }\n\n  @Test\n  fun java_happyPath() {\n    lint()\n      .detector(RawDispatchersUsageDetector())\n      .issues(RawDispatchersUsageDetector.ISSUE)\n      .files(\n        java(\n            \"\"\"\n            package slack;\n\n            class ImplementsNothing {\n            }\n          \"\"\"\n          )\n          .indented()\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun java_happyPath_implementsBoth() {\n    lint()\n      .files(\n        java(\n            \"\"\"\n            package slack;\n\n            import java.io.Serializable;\n            import android.os.Parcelable;\n\n            class ImplementsBoth implements Serializable, Parcelable {\n            }\n          \"\"\"\n          )\n          .indented()\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun java_failure() {\n    lint()\n      .files(\n        java(\n            \"\"\"\n            package slack;\n\n            import java.io.Serializable;\n\n            class BadClass implements Serializable {\n            }\n          \"\"\"\n          )\n          .indented()\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/BadClass.java:5: Error: Don't use Serializable. [SerializableUsage]\n        class BadClass implements Serializable {\n              ~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/TestParameterSiteTargetDetectorTest.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport org.junit.Test\n\nclass TestParameterSiteTargetDetectorTest : BaseSlackLintTest() {\n  override fun getDetector() = TestParameterSiteTargetDetector()\n\n  override fun getIssues() = listOf(TestParameterSiteTargetDetector.ISSUE)\n\n  @Test\n  fun testParameterWithParamSiteTarget() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          class MyTest(\n              @param:com.google.testing.junit.testparameterinjector.TestParameter val myParam: String\n          )\n          \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testParameterWithoutParamSiteTarget() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          class MyTest(\n              @com.google.testing.junit.testparameterinjector.TestParameter val myParam: String\n          )\n          \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/MyTest.kt:3: Error: TestParameter annotation has the wrong site target [TestParameterSiteTarget]\n                      @com.google.testing.junit.testparameterinjector.TestParameter val myParam: String\n                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun testParameterWithWrongSiteTarget() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          class MyTest(\n              @field:com.google.testing.junit.testparameterinjector.TestParameter val myParam: String\n          )\n          \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/MyTest.kt:3: Error: TestParameter annotation has the wrong site target [TestParameterSiteTarget]\n                      @field:com.google.testing.junit.testparameterinjector.TestParameter val myParam: String\n                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun testNonPropertyParameter() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          class MyTest(\n              @com.google.testing.junit.testparameterinjector.TestParameter myParam: String\n          )\n          \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testRegularProperty() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          class MyTest {\n              @com.google.testing.junit.testparameterinjector.TestParameter\n              val myParam: String = \"\"\n          }\n          \"\"\"\n        )\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun testDocumentationExample() {\n    lint()\n      .files(\n        kotlin(\n          \"\"\"\n          class MyTest(\n              @com.google.testing.junit.testparameterinjector.TestParameter val myParam: String\n          )\n          \"\"\"\n        )\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/MyTest.kt:3: Error: TestParameter annotation has the wrong site target [TestParameterSiteTarget]\n                      @com.google.testing.junit.testparameterinjector.TestParameter val myParam: String\n                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 error\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/ViewContextDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint\n\nimport com.android.tools.lint.checks.infrastructure.TestLintTask\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport org.junit.Test\n\nclass ViewContextDetectorTest : BaseSlackLintTest() {\n\n  override fun lint(): TestLintTask {\n    return super.lint().allowClassNameClashes(true)\n  }\n\n  @Test\n  fun test_customViewInternalCaller() {\n    lint()\n      .files(loadStub(\"ViewContextDetectorTestCustomViewInternalCaller.java\"))\n      .run()\n      .expect(\n        \"\"\"\n          src/main/java/ViewContextDetectorTestCustomViewInternalCaller.java:11: Error: Unsafe cast of Context to Activity [CastingViewContextToActivity]\n              Activity a = (Activity) getContext();\n                           ~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun test_externalCallerOnView() {\n    lint()\n      .files(loadStub(\"ViewContextDetectorTestExternalCallerOnView.java\"))\n      .run()\n      .expect(\n        \"\"\"\n          src/main/java/ViewContextDetectorTestExternalCallerOnView.java:12: Error: Unsafe cast of Context to Activity [CastingViewContextToActivity]\n              Activity activity = (Activity) view.getContext();\n                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun test_externalCallerOnCustomView() {\n    lint()\n      .files(loadStub(\"ViewContextDetectorTestExternalCallerOnCustomView.java\"))\n      .run()\n      .expect(\n        \"\"\"\n          src/main/java/ViewContextDetectorTestExternalCallerOnCustomView.java:12: Error: Unsafe cast of Context to Activity [CastingViewContextToActivity]\n              Activity activity = (Activity) view.getContext();\n                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun test_contentProvider() {\n    lint().files(loadStub(\"ViewContextDetectorTestContentProvider.java\")).run().expectClean()\n  }\n\n  override fun getDetector(): Detector {\n    return ViewContextDetector()\n  }\n\n  override fun getIssues(): List<Issue> {\n    return ViewContextDetector.issues\n  }\n\n  /*@Test\n  // need to improve detector to catch errors when the cast occurs later.\n  public void test_shouldFailButDoesNot() {\n    lint()\n        .files(\n            java(\"\" +\n                \"package foo;\\n\" +\n                \"import android.app.Activity;\\n\" +\n                \"import android.content.Context;\\n\" +\n                \"import android.util.AttributeSet;\\n\" +\n                \"import android.widget.TextView;\\n\" +\n                \"public class Example {\\n\" +\n                \"  TextView view;\\n\" +\n                \"  public Example(TextView v) {\\n\" +\n                \"    view = v;\\n\" +\n                \"  }\\n\" +\n                \"  public void bar() {\\n\" +\n                \"    Context context = view.getContext();\\n\" +\n                \"    Activity a = (Activity) context;\\n\" +\n                \"  }\\n\" +\n                \"}\\n\"\n            )\n        )\n        .issues(ViewContextDetector.ISSUE_VIEW_CONTEXT_CAST)\n        .run()\n        .expectClean();\n  }*/\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/denylistedapis/DenyListedApiDetectorTest.kt",
    "content": "// Copyright Square, Inc.\n// Apache-2.0\npackage slack.lint.denylistedapis\n\nimport com.android.tools.lint.checks.infrastructure.TestMode.Companion.FULLY_QUALIFIED\nimport com.android.tools.lint.checks.infrastructure.TestMode.Companion.IMPORT_ALIAS\nimport com.android.tools.lint.detector.api.Detector\nimport org.junit.Ignore\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\n// Adapted from https://gist.github.com/JakeWharton/19b1e7b8d5c648b2935ba89148b791ed\nclass DenyListedApiDetectorTest : BaseSlackLintTest() {\n\n  override fun getIssues() = DenyListedApiDetector.ISSUES\n\n  override fun getDetector(): Detector = DenyListedApiDetector()\n\n  @Test\n  fun `flag function with params in deny list`() {\n    lint()\n      .files(\n        CONTEXT_COMPAT_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import android.content.Context\n          import android.graphics.drawable.Drawable\n          import androidx.core.content.ContextCompat\n\n          class SomeView(context: Context) {\n            init {\n              ContextCompat.getDrawable(context, 42)\n            }\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeView.kt:9: Error: Use Context#getDrawableCompat() instead [DenyListedApi]\n            ContextCompat.getDrawable(context, 42)\n                          ~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `allow function not in deny list`() {\n    lint()\n      .files(\n        OBSERVABLE_STUB,\n        TEST_OBSERVER_STUB,\n        RX_RULE_STUB,\n        kotlin(\n            \"\"\"\n          package cash\n\n          import io.reactivex.rxjava3.core.Observable\n          import io.reactivex.rxjava3.observers.TestObserver\n          import com.squareup.util.rx3.test.test\n          import com.squareup.util.rx3.test.RxRule\n\n          class FooTest {\n            @get:Rule val rxRule = RxRule()\n\n            fun test() {\n              observable().test(rxRule).assertValue(42)\n            }\n\n            fun observable(): Observable<String> = TODO()\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `setOnClickListener with null argument in deny list`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import android.view.View;\n\n          class SomeView(view: View) {\n            init {\n              view.setOnClickListener(null);\n            }\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeView.kt:7: Error: This fails to also set View#isClickable. Use View#clearOnClickListener() instead [DenyListedApi]\n            view.setOnClickListener(null);\n                 ~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `setOnClickListener with non-null argument not in deny list`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import android.view.View;\n\n          class SomeView(view: View) {\n            init {\n              view.setOnClickListener {\n                // do something\n              }\n            }\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `setId with explicit id not in deny list`() {\n    lint()\n      .files(\n        VIEWPAGER2_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import androidx.viewpager2.widget.ViewPager2;\n\n          class SomeView(view: ViewPager2) {\n            init {\n              view.setId(1)\n            }\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `setId with ViewCompat#generateViewId() in deny list`() {\n    lint()\n      .files(\n        VIEWCOMPAT_STUB,\n        VIEWPAGER2_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import androidx.viewpager2.widget.ViewPager2;\n          import androidx.core.view.ViewCompat;\n\n          class SomeView(view: ViewPager2) {\n            init {\n              view.setId(ViewCompat.generateViewId())\n            }\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .skipTestModes(FULLY_QUALIFIED, IMPORT_ALIAS) // TODO relies on non-qualified matching.\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeView.kt:8: Error: Use an id defined in resources or a statically created instead of generating with ViewCompat.generateViewId(). See https://issuetracker.google.com/issues/185820237 [DenyListedApi]\n            view.setId(ViewCompat.generateViewId())\n                 ~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `setId with View#generateViewId() in deny list`() {\n    lint()\n      .files(\n        VIEWPAGER2_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import androidx.viewpager2.widget.ViewPager2;\n          import android.view.View;\n\n          class SomeView(view: ViewPager2) {\n            init {\n              view.setId(View.generateViewId())\n            }\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .skipTestModes(FULLY_QUALIFIED, IMPORT_ALIAS) // TODO relies on non-qualified matching.\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeView.kt:8: Error: Use an id defined in resources or a statically created instead of generating with View.generateViewId(). See https://issuetracker.google.com/issues/185820237 [DenyListedApi]\n            view.setId(View.generateViewId())\n                 ~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun errorLinkedList() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.util.LinkedList\n\n          class SomeClass {\n            val stuff = LinkedList<String>()\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: For a stack/queue/double-ended queue use ArrayDeque, for a list use ArrayList. Both are more efficient internally. [DenyListedApi]\n          val stuff = LinkedList<String>()\n                      ~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun errorStack() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.util.Stack\n\n          class SomeClass {\n            val stuff = Stack<String>()\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: For a stack use ArrayDeque which is more efficient internally. [DenyListedApi]\n          val stuff = Stack<String>()\n                      ~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun errorVector() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.util.Vector\n\n          class SomeClass {\n            val stuff = Vector<String>()\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: For a vector use ArrayList or ArrayDeque which are more efficient internally. [DenyListedApi]\n          val stuff = Vector<String>()\n                      ~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun schedulersNewThread() {\n    lint()\n      .files(\n        SCHEDULERS_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import io.reactivex.rxjava3.schedulers.Schedulers\n\n          class SomeClass {\n            val scheduler = Schedulers.newThread()\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Use a scheduler which wraps a cached set of threads. There should be no reason to be arbitrarily creating threads on Android. [DenyListedApi]\n          val scheduler = Schedulers.newThread()\n                                     ~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Ignore(\"Revisiting after we look more into how this would conflict with MagicNumber\")\n  @Test\n  fun buildVersionCodes() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import android.os.Build\n          import android.os.Build.VERSION_CODES\n          import android.os.Build.VERSION_CODES.S\n\n          class SomeClass {\n            val p = android.os.Build.VERSION_CODES.P\n            val q = Build.VERSION_CODES.Q\n            val r = VERSION_CODES.R\n            val s = S\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:5: Error: No one remembers what these constants map to. Use the API level integer value directly since it's self-defining. [DenyListedApi]\n        import android.os.Build.VERSION_CODES.S\n        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/SomeClass.kt:8: Error: No one remembers what these constants map to. Use the API level integer value directly since it's self-defining. [DenyListedApi]\n          val p = android.os.Build.VERSION_CODES.P\n                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/foo/SomeClass.kt:9: Error: No one remembers what these constants map to. Use the API level integer value directly since it's self-defining. [DenyListedApi]\n          val q = Build.VERSION_CODES.Q\n                  ~~~~~~~~~~~~~~~~~~~~~\n        src/foo/SomeClass.kt:10: Error: No one remembers what these constants map to. Use the API level integer value directly since it's self-defining. [DenyListedApi]\n          val r = VERSION_CODES.R\n                  ~~~~~~~~~~~~~~~\n        4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Ignore(\"Not enabled currently\")\n  @Test\n  fun javaTimeInstantNow() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.time.Instant\n\n          class SomeClass {\n            val now = Instant.now()\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Use com.squareup.cash.util.Clock to get the time. [DenyListedApi]\n          val now = Instant.now()\n                    ~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaUtilDate() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.util.Date\n\n          class SomeClass {\n            val now = Date()\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Use java.time.Instant or java.time.ZonedDateTime instead. There is no reason to use java.util.Date in Java 8+. [DenyListedApi]\n          val now = Date()\n                    ~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTextDateFormatField() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.text.DateFormat\n\n          class SomeClass {\n            val yearField = DateFormat.YEAR_FIELD\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Use java.time.DateTimeFormatter instead. There is no reason to use java.text.DateFormat in Java 8+. [DenyListedApi]\n          val yearField = DateFormat.YEAR_FIELD\n                          ~~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTextDateFormatFunction() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.text.DateFormat\n\n          class SomeClass {\n            val dateFormat = DateFormat.getDateInstance()\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Use java.time.DateTimeFormatter instead. There is no reason to use java.text.DateFormat in Java 8+. [DenyListedApi]\n          val dateFormat = DateFormat.getDateInstance()\n                                      ~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTextSimpleDateFormatField() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.text.SimpleDateFormat\n\n          class SomeClass {\n            val yearField = SimpleDateFormat.YEAR_FIELD\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Use java.time.DateTimeFormatter instead. There is no reason to use java.text.DateFormat in Java 8+. [DenyListedApi]\n          val yearField = SimpleDateFormat.YEAR_FIELD\n                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTextSimpleDateFormatFunction() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.text.SimpleDateFormat\n\n          class SomeClass {\n            val dateFormat = SimpleDateFormat.getDateInstance()\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Use java.time.DateTimeFormatter instead. There is no reason to use java.text.DateFormat in Java 8+. [DenyListedApi]\n          val dateFormat = SimpleDateFormat.getDateInstance()\n                                            ~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaUtilCalendarField() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.util.Calendar\n\n          class SomeClass {\n            val hourOfDay = Calendar.HOUR_OF_DAY\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Use java.time.Instant or java.time.ZonedDateTime instead. There is no reason to use java.util.Calendar in Java 8+. [DenyListedApi]\n          val hourOfDay = Calendar.HOUR_OF_DAY\n                          ~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaUtilCalendarFunction() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          import java.util.Calendar\n\n          class SomeClass {\n            val calendar = Calendar.getInstance()\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Use java.time.Instant or java.time.ZonedDateTime instead. There is no reason to use java.util.Calendar in Java 8+. [DenyListedApi]\n          val calendar = Calendar.getInstance()\n                                  ~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun rxCompletableParameterless() {\n    lint()\n      .files(\n        COROUTINE_SCOPE_STUB,\n        COMPLETABLE_STUB,\n        RX_COMPLETABLE_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import kotlinx.coroutines.rx3.rxCompletable\n\n          class SomeClass {\n            val now = rxCompletable {}\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: rxCompletable defaults to Dispatchers.Default. Provide an explicit dispatcher which can be replaced with a test dispatcher to make your tests more deterministic. [DenyListedApi]\n          val now = rxCompletable {}\n                    ~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun rxSingleParameterless() {\n    lint()\n      .files(\n        COROUTINE_SCOPE_STUB,\n        SINGLE_STUB,\n        RX_SINGLE_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import kotlinx.coroutines.rx3.rxSingle\n\n          class SomeClass {\n            val now = rxSingle { \"a\" }\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: rxSingle defaults to Dispatchers.Default. Provide an explicit dispatcher which can be replaced with a test dispatcher to make your tests more deterministic. [DenyListedApi]\n          val now = rxSingle { \"a\" }\n                    ~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun rxMaybeParameterless() {\n    lint()\n      .files(\n        COROUTINE_SCOPE_STUB,\n        MAYBE_STUB,\n        RX_MAYBE_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import kotlinx.coroutines.rx3.rxMaybe\n\n          class SomeClass {\n            val now = rxMaybe { \"a\" }\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: rxMaybe defaults to Dispatchers.Default. Provide an explicit dispatcher which can be replaced with a test dispatcher to make your tests more deterministic. [DenyListedApi]\n          val now = rxMaybe { \"a\" }\n                    ~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun rxObservableParameterless() {\n    lint()\n      .files(\n        COROUTINE_SCOPE_STUB,\n        TEST_OBSERVER_STUB,\n        OBSERVABLE_STUB,\n        PRODUCER_STUB,\n        RX_OBSERVABLE_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import kotlinx.coroutines.rx3.rxObservable\n\n          class SomeClass {\n            val now = rxObservable { send(\"a\") }\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: rxObservable defaults to Dispatchers.Default. Provide an explicit dispatcher which can be replaced with a test dispatcher to make your tests more deterministic. [DenyListedApi]\n          val now = rxObservable { send(\"a\") }\n                    ~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun rxCompletableWithParameters() {\n    lint()\n      .files(\n        COROUTINE_SCOPE_STUB,\n        COMPLETABLE_STUB,\n        RX_COMPLETABLE_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import kotlinx.coroutines.rx3.rxCompletable\n\n          object MyDispatcher : CoroutineContext\n\n          class SomeClass {\n            val now = rxCompletable(MyDispatcher) {}\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun runCatching() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package foo\n\n          class SomeClass {\n            val result = runCatching {}\n          }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:4: Error: runCatching has hidden issues when used with coroutines as it catches and doesn't rethrow CancellationException. This can interfere with coroutines cancellation handling! Prefer catching specific exceptions based on the current case. [DenyListedApi]\n          val result = runCatching {}\n                       ~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun coroutineRunBlocking() {\n    lint()\n      .files(\n        RUN_BLOCKING_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import kotlinx.coroutines.runBlocking\n\n          class SomeClass {\n            val result = runBlocking {}\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:6: Error: Blocking calls in coroutines can cause deadlocks and application jank. Prefer making the enclosing function a suspend function or refactoring this in a way to use non-blocking calls. If running in a test, use runTest {} or Turbine to test synchronous values. [DenyListedBlockingApi]\n          val result = runBlocking {}\n                       ~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun rxJavaBlocking() {\n    lint()\n      .files(\n        COMPLETABLE_STUB,\n        SINGLE_STUB,\n        MAYBE_STUB,\n        OBSERVABLE_STUB,\n        TEST_OBSERVER_STUB,\n        FLOWABLE_STUB,\n        TEST_SUBSCRIBER_STUB,\n        kotlin(\n            \"\"\"\n          package foo\n\n          import io.reactivex.rxjava3.core.Completable\n          import io.reactivex.rxjava3.core.Single\n          import io.reactivex.rxjava3.core.Maybe\n          import io.reactivex.rxjava3.core.Observable\n          import io.reactivex.rxjava3.core.Flowable\n\n          class SomeClass {\n            val singleCase = Single.never<Int>().blockingGet()\n            val singleTest = Single.never<Int>().test()\n            val maybeCase = Maybe.never<Int>().blockingGet()\n            val maybeTest = Maybe.never<Int>().test()\n            val observableCase = Observable.never<Int>().blockingFirst()\n            val observableTest = Observable.never<Int>().test()\n            val flowableCase = Flowable.never<Int>().blockingFirst()\n            val flowableTest = Flowable.never<Int>().test()\n\n            fun test() {\n              Completable.never().blockingAwait()\n              Completable.never().test()\n            }\n          }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/foo/SomeClass.kt:10: Error: Blocking calls in RxJava can cause deadlocks and application jank. Prefer making the enclosing method/function return this Single, a Disposable to grant control to the caller, Completable (if you want to hide emission values but defer subscription), or refactoring this in a way to use non-blocking calls. If running in a test, use the .test()/TestObserver API (https://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/observers/TestObserver.html) test synchronous values. [DenyListedBlockingApi]\n          val singleCase = Single.never<Int>().blockingGet()\n                                               ~~~~~~~~~~~\n        src/foo/SomeClass.kt:12: Error: Blocking calls in RxJava can cause deadlocks and application jank. Prefer making the enclosing method/function return this Maybe, a Disposable to grant control to the caller, Completable (if you want to hide emission values but defer subscription), or refactoring this in a way to use non-blocking calls. If running in a test, use the .test()/TestObserver API (https://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/observers/TestObserver.html) test synchronous values. [DenyListedBlockingApi]\n          val maybeCase = Maybe.never<Int>().blockingGet()\n                                             ~~~~~~~~~~~\n        src/foo/SomeClass.kt:14: Error: Blocking calls in RxJava can cause deadlocks and application jank. Prefer making the enclosing method/function return this Observable, a Disposable to grant control to the caller, Completable (if you want to hide emission values but defer subscription), or refactoring this in a way to use non-blocking calls. If running in a test, use the .test()/TestObserver API (https://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/observers/TestObserver.html) test synchronous values. [DenyListedBlockingApi]\n          val observableCase = Observable.never<Int>().blockingFirst()\n                                                       ~~~~~~~~~~~~~\n        src/foo/SomeClass.kt:16: Error: Blocking calls in RxJava can cause deadlocks and application jank. Prefer making the enclosing method/function return this Flowable, a Disposable to grant control to the caller, Completable (if you want to hide emission values but defer subscription), or refactoring this in a way to use non-blocking calls. If running in a test, use the .test()/TestObserver API (https://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/observers/TestObserver.html) test synchronous values. [DenyListedBlockingApi]\n          val flowableCase = Flowable.never<Int>().blockingFirst()\n                                                   ~~~~~~~~~~~~~\n        src/foo/SomeClass.kt:20: Error: Blocking calls in RxJava can cause deadlocks and application jank. Prefer making the enclosing method/function return this Completable, a Disposable to grant control to the caller, or refactoring this in a way to use non-blocking calls. If running in a test, use the .test()/TestObserver API (https://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/observers/TestObserver.html) test synchronous values. [DenyListedBlockingApi]\n            Completable.never().blockingAwait()\n                                ~~~~~~~~~~~~~\n        5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  companion object {\n    private val FLOWABLE_STUB =\n      java(\n          \"\"\"\n        package io.reactivex.rxjava3.core;\n\n        import io.reactivex.rxjava3.subscribers.TestSubscriber;\n\n        public final class Flowable<T> {\n          public static <T> Flowable<T> just(T item) {}\n          public static <T> Flowable<T> never() {\n            return new Flowable<>();\n          }\n          public T blockingFirst() {\n            return null;\n          }\n          public TestSubscriber<T> test() {}\n          public TestSubscriber<T> test(boolean dispose) {}\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val TEST_SUBSCRIBER_STUB =\n      java(\n          \"\"\"\n        package io.reactivex.rxjava3.subscribers;\n\n        public class TestSubscriber<T> {\n          public final assertValue(T value) {}\n        }\n      \"\"\"\n        )\n        .indented()\n    private val OBSERVABLE_STUB =\n      java(\n          \"\"\"\n        package io.reactivex.rxjava3.core;\n\n        import io.reactivex.rxjava3.observers.TestObserver;\n\n        public final class Observable<T> {\n          public static <T> Observable<T> just(T item) {}\n          public static <T> Observable<T> never() {\n            return new Observable<>();\n          }\n          public T blockingFirst() {\n            return null;\n          }\n          public TestObserver<T> test() {}\n          public TestObserver<T> test(boolean dispose) {}\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val TEST_OBSERVER_STUB =\n      java(\n          \"\"\"\n        package io.reactivex.rxjava3.observers;\n\n        public final class TestObserver<T> {\n          public assertValue(T value) {}\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val RX_RULE_STUB =\n      kotlin(\n          \"\"\"\n        package com.squareup.util.rx3.test\n\n        import io.reactivex.rxjava3.core.Observable\n\n        class RxRule {\n          fun <T : Any> newObserver(): RecordingObserver<T> = TODO()\n        }\n\n        fun <T: Any> Observable<T>.test(rxRule: RxRule): RecordingObserver<T>\n      \"\"\"\n        )\n        .indented()\n\n    private val CONTEXT_COMPAT_STUB =\n      java(\n          \"\"\"\n        package androidx.core.content;\n\n        import android.graphics.drawable.Drawable;\n        import android.content.Context;\n\n        public class ContextCompat {\n          public static Drawable getDrawable(Context context, int id) {}\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val VIEWCOMPAT_STUB =\n      java(\n          \"\"\"\n        package androidx.core.view;\n\n        public class ViewCompat {\n          public static int generateViewId() { return 0; }\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val VIEWPAGER2_STUB =\n      java(\n          \"\"\"\n        package androidx.viewpager2.widget;\n\n        public class ViewPager2 {\n          public void setId(int id) {}\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val SCHEDULERS_STUB =\n      java(\n          \"\"\"\n        package io.reactivex.rxjava3.schedulers;\n\n        public final class Schedulers {\n          public static Object newThread() {\n            return null;\n          }\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val COROUTINE_SCOPE_STUB =\n      kotlin(\n          \"\"\"\n        package kotlinx.coroutines\n\n        interface CoroutineScope\n      \"\"\"\n        )\n        .indented()\n\n    private val COMPLETABLE_STUB =\n      java(\n          \"\"\"\n        package io.reactivex.rxjava3.core;\n\n        public final class Completable {\n          Completable() {}\n\n          public static Completable never() {\n            return new Completable();\n          }\n\n          public void blockingAwait() {\n\n          }\n\n          public TestObserver<Void> test() {\n            return new TestObserver<>();\n          }\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val RX_COMPLETABLE_STUB =\n      kotlin(\n          \"\"\"\n        package kotlinx.coroutines.rx3\n\n        import kotlin.coroutines.CoroutineContext\n        import kotlin.coroutines.EmptyCoroutineContext\n        import kotlinx.coroutines.CoroutineScope\n\n        class RxCompletable {}\n\n        public fun rxCompletable(\n            context: CoroutineContext = EmptyCoroutineContext,\n            block: suspend CoroutineScope.() -> Unit\n        ): Completable {\n          return Completable\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val SINGLE_STUB =\n      java(\n          \"\"\"\n        package io.reactivex.rxjava3.core;\n\n        public final class Single<T> {\n          Single() {}\n\n          public static <T> Single<T> never() {\n            return new Single<>();\n          }\n\n          public T blockingGet() {\n            return null;\n          }\n\n          public TestObserver<T> test() {\n            return new TestObserver<>();\n          }\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val RX_SINGLE_STUB =\n      kotlin(\n          \"\"\"\n        package kotlinx.coroutines.rx3\n\n        import kotlin.coroutines.CoroutineContext\n        import kotlin.coroutines.EmptyCoroutineContext\n        import kotlinx.coroutines.CoroutineScope\n\n        class RxSingle {}\n\n        public fun <T> rxSingle(\n            context: CoroutineContext = EmptyCoroutineContext,\n            block: suspend CoroutineScope.() -> T\n        ): Single<T> {\n          return Single<T>()\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val MAYBE_STUB =\n      java(\n          \"\"\"\n        package io.reactivex.rxjava3.core;\n\n        public final class Maybe<T> {\n          Maybe() {}\n\n          public static <T> Maybe<T> never() {\n            return new Maybe<>();\n          }\n\n          public T blockingGet() {\n            return null;\n          }\n\n          public TestObserver<T> test() {\n            return new TestObserver<>();\n          }\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val RX_MAYBE_STUB =\n      kotlin(\n          \"\"\"\n        package kotlinx.coroutines.rx3\n\n        import kotlin.coroutines.CoroutineContext\n        import kotlin.coroutines.EmptyCoroutineContext\n        import kotlinx.coroutines.CoroutineScope\n\n        class rxMaybe {}\n\n        public fun <T> rxMaybe(\n            context: CoroutineContext = EmptyCoroutineContext,\n            block: suspend CoroutineScope.() -> T?\n        ): Maybe<T> {\n          return Maybe<T>()\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val PRODUCER_STUB =\n      kotlin(\n          \"\"\"\n        package kotlinx.coroutines.channels\n\n        object ProducerScope<T> {\n          suspend fun send(value: T)\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val RUN_BLOCKING_STUB =\n      kotlin(\n          \"\"\"\n        @file:JvmName(\"BuildersKt\")\n        package kotlinx.coroutines\n\n        fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {\n          TODO()\n        }\n      \"\"\"\n        )\n        .indented()\n\n    private val RX_OBSERVABLE_STUB =\n      kotlin(\n          \"\"\"\n        package kotlinx.coroutines.rx3\n\n        import kotlin.coroutines.CoroutineContext\n        import kotlin.coroutines.EmptyCoroutineContext\n        import kotlinx.coroutines.CoroutineScope\n        import kotlinx.coroutines.channels.ProducerScope\n\n        class RxObservable {}\n\n        public fun <T> rxObservable(\n            context: CoroutineContext = EmptyCoroutineContext,\n            block: suspend ProducerScope<T>.() -> Unit\n        ): Observable<T> {\n          return Observable<T>()\n        }\n      \"\"\"\n        )\n        .indented()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/eithernet/DoNotExposeEitherNetInRepositoriesDetectorTest.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.eithernet\n\nimport com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nprivate val API_RESULT =\n  kotlin(\n      \"\"\"\n  package com.slack.eithernet\n\n  interface ApiResult<out T : Any, out E : Any>\n\"\"\"\n    )\n    .indented()\n\nclass DoNotExposeEitherNetInRepositoriesDetectorTest : BaseSlackLintTest() {\n  override fun getDetector() = DoNotExposeEitherNetInRepositoriesDetector()\n\n  override fun getIssues() = listOf(DoNotExposeEitherNetInRepositoriesDetector.ISSUE)\n\n  // TODO fix these\n  override val skipTestModes: Array<TestMode> = arrayOf(TestMode.SUPPRESSIBLE)\n\n  @Test\n  fun javaTests() {\n    lint()\n      .files(\n        API_RESULT,\n        java(\n            \"\"\"\n        package test;\n\n        import com.slack.eithernet.ApiResult;\n\n        interface MyRepository {\n          // Bad\n\n          ApiResult<String, Exception> getResult();\n\n          // Good\n\n          String getString();\n        }\n      \"\"\"\n          )\n          .indented(),\n\n        // Non-interface version\n        java(\n            \"\"\"\n        package test;\n\n        import com.slack.eithernet.ApiResult;\n\n        abstract class MyClassRepository {\n          // Bad\n\n          public abstract ApiResult<String, Exception> getResultPublic();\n          public ApiResult<String, Exception> resultField = null;\n\n          // Good\n\n          ApiResult<String, Exception> resultFieldPackagePrivate = null;\n          private final ApiResult<String, Exception> resultFieldPrivate = null;\n          protected ApiResult<String, Exception> resultFieldProtected = null;\n          abstract ApiResult<String, Exception> getResultPackagePrivate();\n          private ApiResult<String, Exception> getResultPrivate();\n          private ApiResult<String, Exception> getResultProtected();\n          public abstract String getString();\n        }\n      \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/MyClassRepository.java:8: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n          public abstract ApiResult<String, Exception> getResultPublic();\n                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/test/MyClassRepository.java:9: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n          public ApiResult<String, Exception> resultField = null;\n                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/test/MyRepository.java:8: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n          ApiResult<String, Exception> getResult();\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun kotlinTests() {\n    lint()\n      .files(\n        API_RESULT,\n        kotlin(\n            \"\"\"\n        package test\n\n        import com.slack.eithernet.ApiResult\n\n        interface MyRepository {\n          // Bad\n\n          fun getResult(): ApiResult<String, Exception>\n          suspend fun getResultSuspended(): ApiResult<String, Exception>\n          val resultVal: ApiResult<String, Exception>\n\n          // Good\n\n          fun getString(): String\n          suspend fun getStringSuspended(): String\n          val stringValue: String\n        }\n      \"\"\"\n          )\n          .indented(),\n\n        // Non-interface version\n        kotlin(\n            \"\"\"\n        package test\n\n        import com.slack.eithernet.ApiResult\n\n        abstract class MyClassRepository {\n          // Bad\n\n          abstract fun getResultPublic(): ApiResult<String, Exception>\n          fun typeLessFunction() = getResultPublic()\n          val resultProperty: ApiResult<String, Exception>? = null\n          val typeLessProperty get() = resultProperty\n\n          // Good\n\n          internal val resultPropertyInternal: ApiResult<String, Exception>? = null\n          private val resultPropertyPrivate: ApiResult<String, Exception>? = null\n          protected val resultPropertyProtected: ApiResult<String, Exception>? = null\n          internal abstract fun getResultInternal(): ApiResult<String, Exception>\n          private fun getResultPrivate(): ApiResult<String, Exception>\n          private fun getResultProtected(): ApiResult<String, Exception>\n          abstract fun getString(): String\n        }\n      \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/MyClassRepository.kt:8: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n          abstract fun getResultPublic(): ApiResult<String, Exception>\n                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/test/MyClassRepository.kt:9: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n          fun typeLessFunction() = getResultPublic()\n              ~~~~~~~~~~~~~~~~\n        src/test/MyClassRepository.kt:10: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n          val resultProperty: ApiResult<String, Exception>? = null\n                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/test/MyClassRepository.kt:11: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n          val typeLessProperty get() = resultProperty\n              ~~~~~~~~~~~~~~~~\n        src/test/MyRepository.kt:8: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n          fun getResult(): ApiResult<String, Exception>\n                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        src/test/MyRepository.kt:9: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n          suspend fun getResultSuspended(): ApiResult<String, Exception>\n                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        6 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun regressionTest() {\n    lint()\n      .files(\n        API_RESULT,\n        kotlin(\n            \"\"\"\n        package test\n\n        import com.slack.eithernet.ApiResult\n\n        interface StuffRepository {\n\n          suspend fun fetchStuff(): ApiResult<String, String>\n\n          suspend fun setStuff(\n            discoverability: String\n          ): ApiResult<Unit, String>\n        }\n      \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n          src/test/StuffRepository.kt:7: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n            suspend fun fetchStuff(): ApiResult<String, String>\n                                      ~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/test/StuffRepository.kt:11: Error: Repository APIs should not expose EitherNet types directly. [DoNotExposeEitherNetInRepositories]\n            ): ApiResult<Unit, String>\n               ~~~~~~~~~~~~~~~~~~~~~~~\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/inclusive/InclusiveNamingDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.inclusive\n\nimport com.android.tools.lint.checks.infrastructure.TestLintTask\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Issue\nimport org.junit.Ignore\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass InclusiveNamingDetectorTest : BaseSlackLintTest() {\n\n  override fun getDetector(): Detector = InclusiveNamingSourceCodeScanner()\n\n  override fun getIssues(): List<Issue> = InclusiveNamingChecker.ISSUES.toList()\n\n  override fun lint(): TestLintTask {\n    return super.lint().configureOption(InclusiveNamingChecker.BLOCK_LIST, \"fork,knife,spoon,spork\")\n  }\n\n  override val skipTestModes: Array<TestMode> =\n    arrayOf(\n      // TODO fix these\n      TestMode.SUPPRESSIBLE,\n      // Aliases are impossible to test correctly because you have to maintain completely different\n      // expected fixes and source inputs\n      TestMode.TYPE_ALIAS,\n    )\n\n  @Test\n  fun kotlin() {\n    // This covers the following cases:\n    // - Class\n    // - Parameter\n    // - Property\n    // - Local var\n    // - Label\n    // - Function\n    lint()\n      .files(\n        kotlin(\n            \"test/ForkHandler.kt\",\n            \"\"\"\n              class Spork(val sporkBranch: String, sporkParam: String) {\n                val knife = \"\"\n\n                fun spoonBranch(val spoonRef: String) {\n                  val localFork = \"\"\n                  emptyList<String>()\n                      .forEach spoonRefs@ {\n\n                       }\n                }\n              }\n            \"\"\",\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/ForkHandler.kt:1: Error: Use inclusive naming. Matched string is 'fork' in file name 'ForkHandler.kt' [InclusiveNaming]\n          class Spork(val sporkBranch: String, sporkParam: String) {\n          ^\n          test/ForkHandler.kt:1: Error: Use inclusive naming. Matched string is 'spork' in class name 'Spork' [InclusiveNaming]\n          class Spork(val sporkBranch: String, sporkParam: String) {\n          ^\n          test/ForkHandler.kt:1: Error: Use inclusive naming. Matched string is 'spork' in function name 'getSporkBranch' [InclusiveNaming]\n          class Spork(val sporkBranch: String, sporkParam: String) {\n                          ~~~~~~~~~~~\n          test/ForkHandler.kt:1: Error: Use inclusive naming. Matched string is 'spork' in parameter name 'sporkParam' [InclusiveNaming]\n          class Spork(val sporkBranch: String, sporkParam: String) {\n                                               ~~~~~~~~~~~~~~~~~~\n          test/ForkHandler.kt:1: Error: Use inclusive naming. Matched string is 'spork' in property name 'sporkBranch' [InclusiveNaming]\n          class Spork(val sporkBranch: String, sporkParam: String) {\n                      ~~~~~~~~~~~~~~~~~~~~~~~\n          test/ForkHandler.kt:2: Error: Use inclusive naming. Matched string is 'knife' in function name 'getKnife' [InclusiveNaming]\n            val knife = \"\"\n                ~~~~~\n          test/ForkHandler.kt:2: Error: Use inclusive naming. Matched string is 'knife' in property name 'knife' [InclusiveNaming]\n            val knife = \"\"\n            ~~~~~~~~~~~~~~\n          test/ForkHandler.kt:4: Error: Use inclusive naming. Matched string is 'spoon' in function name 'spoonBranch' [InclusiveNaming]\n            fun spoonBranch(val spoonRef: String) {\n                ~~~~~~~~~~~\n          test/ForkHandler.kt:4: Error: Use inclusive naming. Matched string is 'spoon' in parameter name 'spoonRef' [InclusiveNaming]\n            fun spoonBranch(val spoonRef: String) {\n                            ~~~~~~~~~~~~~~~~~~~~\n          test/ForkHandler.kt:5: Error: Use inclusive naming. Matched string is 'fork' in local variable name 'localFork' [InclusiveNaming]\n              val localFork = \"\"\n              ~~~~~~~~~~~~~~~~~~\n          test/ForkHandler.kt:7: Error: Use inclusive naming. Matched string is 'spoon' in label name 'spoonRefs' [InclusiveNaming]\n                  .forEach spoonRefs@ {\n                           ^\n          11 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun java() {\n    // This covers the following cases:\n    // - Class\n    // - Parameter\n    // - Field\n    // - Local var\n    // - Method\n    lint()\n      .files(\n        java(\n            \"test/ForkHandler.java\",\n            \"\"\"\n              class ForkHandler {\n                String knife = \"\";\n\n                void spoonBranch(String spoonRef) {\n                  String localFork = \"\";\n                }\n              }\n            \"\"\",\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n          test/ForkHandler.java:1: Error: Use inclusive naming. Matched string is 'fork' in class name 'ForkHandler' [InclusiveNaming]\n          class ForkHandler {\n          ^\n          test/ForkHandler.java:1: Error: Use inclusive naming. Matched string is 'fork' in file name 'ForkHandler.java' [InclusiveNaming]\n          class ForkHandler {\n          ^\n          test/ForkHandler.java:2: Error: Use inclusive naming. Matched string is 'knife' in field name 'knife' [InclusiveNaming]\n            String knife = \"\";\n            ~~~~~~~~~~~~~~~~~~\n          test/ForkHandler.java:4: Error: Use inclusive naming. Matched string is 'spoon' in method name 'spoonBranch' [InclusiveNaming]\n            void spoonBranch(String spoonRef) {\n                 ~~~~~~~~~~~\n          test/ForkHandler.java:4: Error: Use inclusive naming. Matched string is 'spoon' in parameter name 'spoonRef' [InclusiveNaming]\n            void spoonBranch(String spoonRef) {\n                             ~~~~~~~~~~~~~~~\n          test/ForkHandler.java:5: Error: Use inclusive naming. Matched string is 'fork' in local variable name 'localFork' [InclusiveNaming]\n              String localFork = \"\";\n              ~~~~~~~~~~~~~~~~~~~~~~\n          6 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Ignore(\"Not working juuuuuust yet. Left as a toe-hold\")\n  @Test\n  fun xml() {\n    // Attr\n    lint()\n      .files(\n        xml(\n            \"test_file.xml\",\n            \"\"\"\n              <?xml version=\"1.0\" encoding=\"utf-8\"?>\n              <com.example.SomeView\n                  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                  android:masterAttribute=\"testing\"\n                  />\n            \"\"\",\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/AutoValueMockDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass AutoValueMockDetectorTest : BaseSlackLintTest() {\n\n  private val autoValueAnnotationClass =\n    kotlin(\n      \"\"\"\n        package com.google.auto.value\n\n        annotation class AutoValue {\n\n          annotation class Builder\n        }\n      \"\"\"\n    )\n\n  private val testClass =\n    java(\n        \"\"\"\n      package slack.test;\n\n      import com.google.auto.value.AutoValue;\n\n      @AutoValue\n      public abstract class TestClass {\n        public static Builder builder() {\n          return null;\n        }\n        @AutoValue.Builder\n        public abstract class Builder {\n          abstract TestClass build();\n        }\n      }\n    \"\"\"\n      )\n      .indented()\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import org.mockito.Mock\n          import org.mockito.Spy\n          import slack.test.mockito.mock\n\n          class MyTests {\n            @Mock lateinit var fieldMock: TestClass\n            @Spy lateinit var fieldSpy: TestClass\n            @Mock lateinit var fieldBuilderMock: TestClass.Builder\n            @Spy lateinit var fieldBuilderSpy: TestClass.Builder\n\n            fun example() {\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n              val localMock2 = mock<TestClass>()\n              val classRef = TestClass::class.java\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n\n              val builderLocalMock1 = org.mockito.Mockito.mock(TestClass.Builder::class.java)\n              val builderLocalSpy1 = org.mockito.Mockito.spy(builderLocalMock1)\n              val builderLocalMock2 = mock<TestClass.Builder>()\n              val builderClassRef = TestClass.Builder::class.java\n              val builderLocalMock3 = org.mockito.Mockito.mock(classRef)\n              val fake = TestClass.builder().build()\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), autoValueAnnotationClass, testClass, source)\n      .allowCompilationErrors() // Until AGP 7.1.0\n      // https://groups.google.com/g/lint-dev/c/BigCO8sMhKU\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.kt:8: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n            @Mock lateinit var fieldMock: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:9: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n            @Spy lateinit var fieldSpy: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:10: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n            @Mock lateinit var fieldBuilderMock: TestClass.Builder\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:11: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n            @Spy lateinit var fieldBuilderSpy: TestClass.Builder\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:14: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:15: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:16: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n              val localMock2 = mock<TestClass>()\n                               ~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:18: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:20: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n              val builderLocalMock1 = org.mockito.Mockito.mock(TestClass.Builder::class.java)\n                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:21: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n              val builderLocalSpy1 = org.mockito.Mockito.spy(builderLocalMock1)\n                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:22: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n              val builderLocalMock2 = mock<TestClass.Builder>()\n                                      ~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:24: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n              val builderLocalMock3 = org.mockito.Mockito.mock(classRef)\n                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          12 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"test/test/slack/test/TestClass.java\",\n          \"\"\"\n          package slack.test;\n\n          import org.mockito.Mock;\n          import org.mockito.Spy;\n          import static org.mockito.Mockito.mock;\n          import static org.mockito.Mockito.spy;\n\n          class MyTests {\n            @Mock TestClass fieldMock;\n            @Spy TestClass fieldSpy;\n            @Mock TestClass.Builder fieldMock;\n            @Spy TestClass.Builder fieldSpy;\n\n            public void example() {\n              TestClass localMock = mock(TestClass.class);\n              TestClass localSpy = spy(localMock);\n              Class<TestClass> classRef = TestClass.class;\n              TestClass localMock2 = mock(classRef);\n\n              TestClass.Builder builderLocalMock = mock(TestClass.Builder.class);\n              TestClass.Builder builderLocalSpy = spy(builderLocalMock);\n              Class<TestClass.Builder> builderClassRef = TestClass.Builder.class;\n              TestClass.Builder builderLocalMock2 = mock(builderClassRef);\n              TestClass fake = TestClass.builder().build();\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), autoValueAnnotationClass, testClass, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.java:9: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n            @Mock TestClass fieldMock;\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:10: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n            @Spy TestClass fieldSpy;\n            ~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:11: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n            @Mock TestClass.Builder fieldMock;\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:12: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n            @Spy TestClass.Builder fieldSpy;\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:15: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n              TestClass localMock = mock(TestClass.class);\n                                    ~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:16: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n              TestClass localSpy = spy(localMock);\n                                   ~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:18: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue [DoNotMockAutoValue]\n              TestClass localMock2 = mock(classRef);\n                                     ~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:20: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n              TestClass.Builder builderLocalMock = mock(TestClass.Builder.class);\n                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:21: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n              TestClass.Builder builderLocalSpy = spy(builderLocalMock);\n                                                  ~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:23: Error: Mocked type is annotated with non-mockable annotation com.google.auto.value.AutoValue.Builder [DoNotMockAutoValue]\n              TestClass.Builder builderLocalMock2 = mock(builderClassRef);\n                                                    ~~~~~~~~~~~~~~~~~~~~~\n          10 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/DataClassMockDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass DataClassMockDetectorTest : BaseSlackLintTest() {\n\n  private val testClass =\n    kotlin(\n        \"\"\"\n      package slack.test\n\n      data class TestClass(val foo: String, val list: List<TestClass> = emptyList())\n    \"\"\"\n      )\n      .indented()\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import org.mockito.Mock\n          import org.mockito.Spy\n          import slack.test.mockito.mock\n\n          class MyTests {\n            @Mock lateinit var fieldMock: TestClass\n            @Spy lateinit var fieldSpy: TestClass\n\n            fun example() {\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n              val localMock2 = mock<TestClass>()\n              val classRef = TestClass::class.java\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n\n              val dynamicMock = mock<TestClass> {\n\n              }\n              val assigned: TestClass = mock()\n              val fake = TestClass(\"this is fine\")\n\n              // Extra tests for location reporting\n              val unnecessaryMockedValues = TestClass(\n                \"This is fine\",\n                mock()\n              )\n              val unnecessaryNestedMockedValues = TestClass(\n                \"This is fine\",\n                listOf(mock())\n              )\n              val withNamedArgs = TestClass(\n                foo = \"This is fine\",\n                list = listOf(mock())\n              )\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), testClass, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.kt:8: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            @Mock lateinit var fieldMock: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:9: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            @Spy lateinit var fieldSpy: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:12: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:13: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:14: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n              val localMock2 = mock<TestClass>()\n                               ~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:16: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:18: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n              val dynamicMock = mock<TestClass> {\n                                ^\n          test/test/slack/test/TestClass.kt:21: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n              val assigned: TestClass = mock()\n                                        ~~~~~~\n          test/test/slack/test/TestClass.kt:31: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n                listOf(mock())\n                       ~~~~~~\n          test/test/slack/test/TestClass.kt:35: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n                list = listOf(mock())\n                              ~~~~~~\n          test/test/slack/test/TestClass.kt:27: Error: platform type 'java.util.List' should not be mocked [DoNotMockPlatformTypes]\n                mock()\n                ~~~~~~\n          11 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"test/test/slack/test/TestClass.java\",\n          \"\"\"\n          package slack.test;\n\n          import org.mockito.Mock;\n          import org.mockito.Spy;\n          import static org.mockito.Mockito.mock;\n          import static org.mockito.Mockito.spy;\n\n          class MyTests {\n            @Mock TestClass fieldMock;\n            @Spy TestClass fieldSpy;\n\n            public void example() {\n              TestClass localMock = mock(TestClass.class);\n              TestClass localSpy = spy(localMock);\n              Class<TestClass> classRef = TestClass.class;\n              TestClass localMock2 = mock(classRef);\n              TestClass fake = new TestClass(\"this is fine\");\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), testClass, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.java:9: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            @Mock TestClass fieldMock;\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:10: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            @Spy TestClass fieldSpy;\n            ~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:13: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n              TestClass localMock = mock(TestClass.class);\n                                    ~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:14: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n              TestClass localSpy = spy(localMock);\n                                   ~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:16: Error: 'slack.test.TestClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n              TestClass localMock2 = mock(classRef);\n                                     ~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/DoNotMockMockDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass DoNotMockMockDetectorTest : BaseSlackLintTest() {\n\n  private val slackDoNotMock =\n    kotlin(\n        \"\"\"\n      package slack.lint.annotations\n\n      annotation class DoNotMock(val value: String = \"BECAUSE REASONS\")\n    \"\"\"\n      )\n      .indented()\n\n  private val epDoNotMock =\n    kotlin(\n        \"\"\"\n      package com.google.errorprone.annotations\n\n      annotation class DoNotMock(val value: String = \"BECAUSE REASONS\")\n    \"\"\"\n      )\n      .indented()\n\n  private val testClass =\n    kotlin(\n        \"\"\"\n      package slack.test\n\n      @slack.lint.annotations.DoNotMock(\"Use fake()\")\n      interface TestClass {\n        fun fake(): TestClass? = null\n      }\n\n      @com.google.errorprone.annotations.DoNotMock(\"Use fake()\")\n      interface TestClass2 {\n        fun fake(): TestClass2? = null\n      }\n\n      @slack.lint.annotations.DoNotMock\n      interface TestClass3 {\n        fun fake(): TestClass3? = null\n      }\n\n      @com.google.errorprone.annotations.DoNotMock\n      interface TestClass4 {\n        fun fake(): TestClass4? = null\n      }\n    \"\"\"\n      )\n      .indented()\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import org.mockito.Mock\n\n          class MyTests {\n            @Mock lateinit var mock1: TestClass\n            @Mock lateinit var mock2: TestClass2\n            @Mock lateinit var mock3: TestClass3\n            @Mock lateinit var mock4: TestClass4\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), slackDoNotMock, epDoNotMock, testClass, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.kt:6: Error: Do not mock TestClass: Use fake() [DoNotMock]\n            @Mock lateinit var mock1: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:7: Error: Do not mock TestClass2: Use fake() [DoNotMock]\n            @Mock lateinit var mock2: TestClass2\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:8: Error: Do not mock TestClass3: BECAUSE REASONS [DoNotMock]\n            @Mock lateinit var mock3: TestClass3\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:9: Error: Do not mock TestClass4: BECAUSE REASONS [DoNotMock]\n            @Mock lateinit var mock4: TestClass4\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"test/test/slack/test/TestClass.java\",\n          \"\"\"\n          package slack.test;\n\n          import org.mockito.Mock;\n          import org.mockito.Spy;\n          import static org.mockito.Mockito.mock;\n          import static org.mockito.Mockito.spy;\n\n          class MyTests {\n            @Mock TestClass mock;\n            @Mock TestClass2 mock2;\n            @Mock TestClass3 mock3;\n            @Mock TestClass4 mock4;\n\n            public void example() {\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), slackDoNotMock, epDoNotMock, testClass, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.java:9: Error: Do not mock TestClass: Use fake() [DoNotMock]\n            @Mock TestClass mock;\n            ~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:10: Error: Do not mock TestClass2: Use fake() [DoNotMock]\n            @Mock TestClass2 mock2;\n            ~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:11: Error: Do not mock TestClass3: BECAUSE REASONS [DoNotMock]\n            @Mock TestClass3 mock3;\n            ~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:12: Error: Do not mock TestClass4: BECAUSE REASONS [DoNotMock]\n            @Mock TestClass4 mock4;\n            ~~~~~~~~~~~~~~~~~~~~~~~\n          4 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/DoNotMockUsageDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass DoNotMockUsageDetectorTest : BaseSlackLintTest() {\n\n  // TODO fix these\n  override val skipTestModes: Array<TestMode> = arrayOf(TestMode.TYPE_ALIAS, TestMode.IMPORT_ALIAS)\n\n  private val slackDoNotMock =\n    kotlin(\n        \"\"\"\n      package slack.lint.annotations\n\n      annotation class DoNotMock(val value: String = \"BECAUSE REASONS\")\n    \"\"\"\n      )\n      .indented()\n\n  private val epDoNotMock =\n    kotlin(\n        \"\"\"\n      package com.google.errorprone.annotations\n\n      annotation class DoNotMock(val value: String = \"BECAUSE REASONS\")\n    \"\"\"\n      )\n      .indented()\n\n  override fun getDetector() = ErrorProneDoNotMockDetector()\n\n  override fun getIssues() = listOf(ErrorProneDoNotMockDetector.ISSUE)\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"\"\"\n      package slack.test\n\n      import com.google.errorprone.annotations.DoNotMock\n\n      @slack.lint.annotations.DoNotMock(\"Use fake()\")\n      interface TestClass {\n        fun fake(): TestClass? = null\n      }\n\n      @com.google.errorprone.annotations.DoNotMock(\"Use fake()\")\n      interface TestClass2 {\n        fun fake(): TestClass2? = null\n      }\n\n      @slack.lint.annotations.DoNotMock\n      interface TestClass3 {\n        fun fake(): TestClass3? = null\n      }\n\n      @DoNotMock\n      interface TestClass4 {\n        fun fake(): TestClass4? = null\n      }\n    \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), slackDoNotMock, epDoNotMock, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/test/TestClass.kt:10: Error: Use Slack's internal @DoNotMock annotation. [ErrorProneDoNotMockUsage]\n          @com.google.errorprone.annotations.DoNotMock(\"Use fake()\")\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/test/TestClass.kt:20: Error: Use Slack's internal @DoNotMock annotation. [ErrorProneDoNotMockUsage]\n          @DoNotMock\n          ~~~~~~~~~~\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/slack/test/TestClass.kt line 10: Replace with slack.lint.annotations.DoNotMock:\n          @@ -10 +10\n          - @com.google.errorprone.annotations.DoNotMock(\"Use fake()\")\n          + @slack.lint.annotations.DoNotMock(\"Use fake()\")\n          Fix for src/slack/test/TestClass.kt line 20: Replace with slack.lint.annotations.DoNotMock:\n          @@ -20 +20\n          - @DoNotMock\n          + @slack.lint.annotations.DoNotMock\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"\"\"\n      package slack.test;\n\n      import com.google.errorprone.annotations.DoNotMock;\n\n      @slack.lint.annotations.DoNotMock(\"Use fake()\")\n      interface TestClass {\n      }\n\n      @com.google.errorprone.annotations.DoNotMock(\"Use fake()\")\n      interface TestClass2 {\n      }\n\n      @slack.lint.annotations.DoNotMock\n      interface TestClass3 {\n      }\n\n      @DoNotMock\n      interface TestClass4 {\n      }\n    \"\"\"\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), slackDoNotMock, epDoNotMock, source)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/test/TestClass.java:9: Error: Use Slack's internal @DoNotMock annotation. [ErrorProneDoNotMockUsage]\n          @com.google.errorprone.annotations.DoNotMock(\"Use fake()\")\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          src/slack/test/TestClass.java:17: Error: Use Slack's internal @DoNotMock annotation. [ErrorProneDoNotMockUsage]\n          @DoNotMock\n          ~~~~~~~~~~\n          2 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/slack/test/TestClass.java line 9: Replace with slack.lint.annotations.DoNotMock:\n          @@ -9 +9\n          - @com.google.errorprone.annotations.DoNotMock(\"Use fake()\")\n          + @slack.lint.annotations.DoNotMock(\"Use fake()\")\n          Fix for src/slack/test/TestClass.java line 17: Replace with slack.lint.annotations.DoNotMock:\n          @@ -17 +17\n          - @DoNotMock\n          + @slack.lint.annotations.DoNotMock\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/MockDetectorOptionsTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.checks.infrastructure.TestLintTask\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass MockDetectorOptionsTest : BaseSlackLintTest() {\n\n  override fun lint(): TestLintTask {\n    return super.lint()\n      .configureOption(\n        MockDetector.MOCK_ANNOTATIONS,\n        \"io.mockk.impl.annotations.MockK,io.mockk.impl.annotations.SpyK\",\n      )\n      .configureOption(\n        MockDetector.MOCK_FACTORIES,\n        \"io.mockk.MockKKt#mockk,io.mockk.MockKKt#mockkClass,io.mockk.MockKKt#mockkObject,io.mockk.MockKKt#spyk\",\n      )\n  }\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  @Test\n  fun tests() {\n    val source =\n      kotlin(\n          \"test/slack/test/MyTests.kt\",\n          \"\"\"\n          package slack.test\n\n          import io.mockk.impl.annotations.MockK\n          import io.mockk.impl.annotations.SpyK\n          import io.mockk.mockk\n          import io.mockk.mockkClass\n          import io.mockk.mockkObject\n          import io.mockk.spyk\n\n          class MyTests {\n            @MockK lateinit var fieldDataMock: DataClass\n            @SpyK lateinit var fieldDataSpy: DataClass\n\n            fun dataClass() {\n              val localMock1: DataClass = mockk()\n              val localMock2 = mockk<DataClass>()\n\n              val localSpy1: DataClass = spyk()\n              val localSpy2 = spyk<DataClass>()\n              val localSpy3 = spyk(localMock1)\n\n              // KClass<DataClass> is not detected, explicit type needed!\n              val localClassMock = mockkClass<DataClass>(DataClass::class)\n            }\n\n            @MockK lateinit var fieldSealedMock: SealedClass\n            @SpyK lateinit var fieldSealedSpy: SealedClass\n\n            fun sealedClass() {\n              val localMock1: SealedClass = mockk()\n              val localMock2 = mockk<SealedClass>()\n\n              val localSpy1: SealedClass = spyk()\n              val localSpy2 = spyk<SealedClass>()\n              val localSpy3 = spyk(localMock1)\n\n              // KClass<SealedClass> is not detected, explicit type needed!\n              val localClassMock = mockkClass<SealedClass>(SealedClass::class)\n            }\n\n            @MockK lateinit var fieldObjectMock: ObjectClass\n            @SpyK lateinit var fieldObjectSpy: ObjectClass\n\n            fun objectClass() {\n              val localMock1: ObjectClass = mockk()\n              val localMock2 = mockk<ObjectClass>()\n\n              val localSpy1: ObjectClass = spyk()\n              val localSpy2 = spyk<ObjectClass>()\n              val localSpy3 = spyk(localMock1)\n\n              // KClass<ObjectClass> is not detected, explicit type needed!\n              val localClassMock = mockkClass<ObjectClass>(ObjectClass::class)\n\n              // Wrong Error: 'kotlin.Unit' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              // Wrong Warning: platform type 'kotlin.Unit' should not be mocked [DoNotMockPlatformTypes]\n              // mockkObject(ObjectClass)\n            }\n\n            fun platformTypes() {\n              // java.\n              mockk<Comparable<String>>()\n              mockk<Runnable>()\n              // kotlin.\n              mockk<FileTreeWalk>()\n              mockk<Lazy<String>>()\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*stubs(), source)\n      .run()\n      .expect(\n        \"\"\"\n        test/slack/test/MyTests.kt:11: Error: 'slack.test.DataClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n          @MockK lateinit var fieldDataMock: DataClass\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:12: Error: 'slack.test.DataClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n          @SpyK lateinit var fieldDataSpy: DataClass\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:15: Error: 'slack.test.DataClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            val localMock1: DataClass = mockk()\n                                        ~~~~~~~\n        test/slack/test/MyTests.kt:16: Error: 'slack.test.DataClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            val localMock2 = mockk<DataClass>()\n                             ~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:18: Error: 'slack.test.DataClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            val localSpy1: DataClass = spyk()\n                                       ~~~~~~\n        test/slack/test/MyTests.kt:19: Error: 'slack.test.DataClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            val localSpy2 = spyk<DataClass>()\n                            ~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:20: Error: 'slack.test.DataClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            val localSpy3 = spyk(localMock1)\n                            ~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:23: Error: 'slack.test.DataClass' is a data class, so mocking it should not be necessary [DoNotMockDataClass]\n            val localClassMock = mockkClass<DataClass>(DataClass::class)\n                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:41: Error: 'slack.test.ObjectClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n          @MockK lateinit var fieldObjectMock: ObjectClass\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:42: Error: 'slack.test.ObjectClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n          @SpyK lateinit var fieldObjectSpy: ObjectClass\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:45: Error: 'slack.test.ObjectClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            val localMock1: ObjectClass = mockk()\n                                          ~~~~~~~\n        test/slack/test/MyTests.kt:46: Error: 'slack.test.ObjectClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            val localMock2 = mockk<ObjectClass>()\n                             ~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:48: Error: 'slack.test.ObjectClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            val localSpy1: ObjectClass = spyk()\n                                         ~~~~~~\n        test/slack/test/MyTests.kt:49: Error: 'slack.test.ObjectClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            val localSpy2 = spyk<ObjectClass>()\n                            ~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:50: Error: 'slack.test.ObjectClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            val localSpy3 = spyk(localMock1)\n                            ~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:53: Error: 'slack.test.ObjectClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            val localClassMock = mockkClass<ObjectClass>(ObjectClass::class)\n                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:62: Error: platform type 'java.lang.Comparable' should not be mocked [DoNotMockPlatformTypes]\n            mockk<Comparable<String>>()\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:63: Error: platform type 'java.lang.Runnable' should not be mocked [DoNotMockPlatformTypes]\n            mockk<Runnable>()\n            ~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:65: Error: platform type 'kotlin.io.FileTreeWalk' should not be mocked [DoNotMockPlatformTypes]\n            mockk<FileTreeWalk>()\n            ~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:66: Error: platform type 'kotlin.Lazy' should not be mocked [DoNotMockPlatformTypes]\n            mockk<Lazy<String>>()\n            ~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:26: Error: 'slack.test.SealedClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n          @MockK lateinit var fieldSealedMock: SealedClass\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:27: Error: 'slack.test.SealedClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n          @SpyK lateinit var fieldSealedSpy: SealedClass\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:30: Error: 'slack.test.SealedClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            val localMock1: SealedClass = mockk()\n                                          ~~~~~~~\n        test/slack/test/MyTests.kt:31: Error: 'slack.test.SealedClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            val localMock2 = mockk<SealedClass>()\n                             ~~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:33: Error: 'slack.test.SealedClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            val localSpy1: SealedClass = spyk()\n                                         ~~~~~~\n        test/slack/test/MyTests.kt:34: Error: 'slack.test.SealedClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            val localSpy2 = spyk<SealedClass>()\n                            ~~~~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:35: Error: 'slack.test.SealedClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            val localSpy3 = spyk(localMock1)\n                            ~~~~~~~~~~~~~~~~\n        test/slack/test/MyTests.kt:38: Error: 'slack.test.SealedClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            val localClassMock = mockkClass<SealedClass>(SealedClass::class)\n                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        28 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  private val mockK =\n    kotlin(\n        \"test/io/mockk/impl/annotations/MockK.kt\",\n        \"\"\"\n    package io.mockk.impl.annotations\n\n    annotation class MockK\n    \"\"\",\n      )\n      .indented()\n\n  private val spyK =\n    kotlin(\n        \"test/io/mockk/impl/annotations/SpyK.kt\",\n        \"\"\"\n    package io.mockk.impl.annotations\n\n    annotation class SpyK\n    \"\"\",\n      )\n      .indented()\n\n  private val mockKExtensions =\n    kotlin(\n        \"test/io/mockk/MockK.kt\",\n        \"\"\"\n    package io.mockk\n\n    inline fun <reified T : Any> mockk(\n        name: String? = null,\n        relaxed: Boolean = false,\n        vararg moreInterfaces: KClass<*>,\n        relaxUnitFun: Boolean = false,\n        block: T.() -> Unit = {}\n    ): T = TODO()\n\n    inline fun <reified T : Any> spyk(\n        name: String? = null,\n        vararg moreInterfaces: KClass<*>,\n        recordPrivateCalls: Boolean = false,\n        block: T.() -> Unit = {}\n    ): T = TODO()\n\n    inline fun <reified T : Any> spyk(\n        objToCopy: T,\n        name: String? = null,\n        vararg moreInterfaces: KClass<*>,\n        recordPrivateCalls: Boolean = false,\n        block: T.() -> Unit = {}\n    ): T = TODO()\n\n    inline fun <T : Any> mockkClass(\n        type: KClass<T>,\n        name: String? = null,\n        relaxed: Boolean = false,\n        vararg moreInterfaces: KClass<*>,\n        relaxUnitFun: Boolean = false,\n        block: T.() -> Unit = {}\n    ): T = TODO()\n\n    inline fun mockkObject(vararg objects: Any, recordPrivateCalls: Boolean = false): Unit = TODO()\n    \"\"\",\n      )\n      .indented()\n\n  private val dataClass =\n    kotlin(\n        \"\"\"\n    package slack.test\n\n    data class DataClass(val foo: String, val list: List<DataClass> = emptyList())\n    \"\"\"\n      )\n      .indented()\n\n  private val sealedClass =\n    kotlin(\n        \"\"\"\n    package slack.test\n\n    sealed class SealedClass\n    \"\"\"\n      )\n      .indented()\n\n  private val objectClass =\n    kotlin(\n        \"\"\"\n    package slack.test\n\n    object ObjectClass\n    \"\"\"\n      )\n      .indented()\n\n  private fun stubs() = arrayOf(mockK, spyK, mockKExtensions, dataClass, sealedClass, objectClass)\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/MockFileStubs.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.checks.infrastructure.TestFiles.java\nimport com.android.tools.lint.checks.infrastructure.TestFiles.kotlin\n\nprivate val mockito =\n  java(\n      \"\"\"\n      package org.mockito;\n\n      public final class Mockito {\n        public static <T> T mock(Class<T> clazz) {\n          return null;\n        }\n        public static <T> T spy(T instance) {\n          return null;\n        }\n      }\n    \"\"\"\n    )\n    .indented()\n\nprivate val mock =\n  java(\n      \"\"\"\n      package org.mockito;\n\n      public @interface Mock {\n\n      }\n    \"\"\"\n    )\n    .indented()\n\nprivate val spy =\n  java(\n      \"\"\"\n      package org.mockito;\n\n      public @interface Spy {\n\n      }\n    \"\"\"\n    )\n    .indented()\n\nprivate val mockitoHelpers =\n  kotlin(\n      \"test/slack/test/mockito/MockitoHelpers.kt\",\n      \"\"\"\n      package slack.test.mockito\n\n      import org.mockito.Mockito\n\n      inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)\n\n      inline fun <reified T : Any> mock(block: T.() -> Unit): T {\n        return Mockito.mock(T::class.java).apply(block)\n      }\n    \"\"\",\n    )\n    .indented()\n\ninternal fun mockFileStubs() = arrayOf(mockito, mock, spy, mockitoHelpers)\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/MockReportTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.google.common.truth.Truth.assertThat\nimport kotlin.io.path.exists\nimport kotlin.io.path.readText\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.rules.TemporaryFolder\nimport slack.lint.BaseSlackLintTest\nimport slack.lint.mocking.MockDetector.Companion.MOCK_REPORT\n\nclass MockReportTest : BaseSlackLintTest() {\n\n  @Rule @JvmField val tmpFolder = TemporaryFolder()\n\n  private val testClass =\n    kotlin(\n        \"\"\"\n      package slack.test\n\n      data class TestClass(val foo: String, val list: List<TestClass> = emptyList())\n    \"\"\"\n      )\n      .indented()\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import org.mockito.Mock\n          import org.mockito.Spy\n          import slack.test.mockito.mock\n\n          class MyTests {\n            @Mock lateinit var fieldMock: TestClass\n            @Spy lateinit var fieldSpy: TestClass\n\n            fun example() {\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n              val localMock2 = mock<TestClass>()\n              val classRef = TestClass::class.java\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n\n              val dynamicMock = mock<TestClass> {\n\n              }\n              val assigned: TestClass = mock()\n              val fake = TestClass(\"this is fine\")\n\n              // Extra tests for location reporting\n              val unnecessaryMockedValues = TestClass(\n                \"This is fine\",\n                mock()\n              )\n              val unnecessaryNestedMockedValues = TestClass(\n                \"This is fine\",\n                listOf(mock())\n              )\n              val withNamedArgs = TestClass(\n                foo = \"This is fine\",\n                list = listOf(mock())\n              )\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    val task =\n      lint()\n        .rootDirectory(tmpFolder.root)\n        .files(*mockFileStubs(), testClass, source)\n        .configureOption(MOCK_REPORT, MockDetector.MockReportMode.ERRORS.name)\n\n    @Suppress(\"CheckResult\") task.run()\n\n    val reports = tmpFolder.root.toPath().resolve(\"default/app/${MockDetector.MOCK_REPORT_PATH}\")\n    assertThat(reports.exists()).isTrue()\n    assertThat(reports.readText())\n      .isEqualTo(\n        \"\"\"\n          type,isError\n          java.util.List,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"test/test/slack/test/TestClass.java\",\n          \"\"\"\n          package slack.test;\n\n          import org.mockito.Mock;\n          import org.mockito.Spy;\n          import static org.mockito.Mockito.mock;\n          import static org.mockito.Mockito.spy;\n\n          class MyTests {\n            @Mock TestClass fieldMock;\n            @Spy TestClass fieldSpy;\n\n            public void example() {\n              TestClass localMock = mock(TestClass.class);\n              TestClass localSpy = spy(localMock);\n              Class<TestClass> classRef = TestClass.class;\n              TestClass localMock2 = mock(classRef);\n              TestClass fake = new TestClass(\"this is fine\");\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    val task =\n      lint()\n        .rootDirectory(tmpFolder.root)\n        .files(*mockFileStubs(), testClass, source)\n        .configureOption(MOCK_REPORT, MockDetector.MockReportMode.ERRORS.name)\n\n    @Suppress(\"CheckResult\") task.run()\n\n    val reports = tmpFolder.root.toPath().resolve(\"default/app/${MockDetector.MOCK_REPORT_PATH}\")\n    assertThat(reports.exists()).isTrue()\n    assertThat(reports.readText())\n      .isEqualTo(\n        \"\"\"\n          type,isError\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun allMode() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import org.mockito.Mock\n          import org.mockito.Spy\n          import slack.test.mockito.mock\n          import java.lang.Runnable\n\n          interface ExampleInterface\n\n          class MyTests {\n            @Mock lateinit var fieldMock: TestClass\n            @Spy lateinit var fieldSpy: TestClass\n            @Mock lateinit var nonErrorMock: ExampleInterface\n            @Spy lateinit var nonErrorSpy: java.lang.ExampleInterface\n\n            fun example() {\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n              val localMock2 = mock<TestClass>()\n              val classRef = TestClass::class.java\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n              val nonErrorMock1 = org.mockito.Mockito.mock(ExampleInterface::class.java)\n              val nonErrorSpy1 = org.mockito.Mockito.spy(nonErrorMock1)\n              val nonErrorMock2 = mock<ExampleInterface>()\n              val classRef = ExampleInterface::class.java\n              val nonErrorMock3 = org.mockito.Mockito.mock(classRef)\n\n              val dynamicMock = mock<TestClass> {\n\n              }\n              val dynamicNonErrorMock = mock<ExampleInterface> {\n\n              }\n              val assigned: TestClass = mock()\n              val assignedNonError: ExampleInterface = mock()\n              val fake = TestClass(\"this is fine\")\n\n              // Extra tests for location reporting\n              val unnecessaryMockedValues = TestClass(\n                \"This is fine\",\n                mock()\n              )\n              val unnecessaryNestedMockedValues = TestClass(\n                \"This is fine\",\n                listOf(mock())\n              )\n              val withNamedArgs = TestClass(\n                foo = \"This is fine\",\n                list = listOf(mock())\n              )\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    val task =\n      lint()\n        .rootDirectory(tmpFolder.root)\n        .files(*mockFileStubs(), testClass, source)\n        .configureOption(MOCK_REPORT, MockDetector.MockReportMode.ALL.name)\n\n    @Suppress(\"CheckResult\") task.run()\n\n    val reports = tmpFolder.root.toPath().resolve(\"default/app/${MockDetector.MOCK_REPORT_PATH}\")\n    assertThat(reports.exists()).isTrue()\n    assertThat(reports.readText())\n      .isEqualTo(\n        \"\"\"\n          type,isError\n          java.util.List,true\n          slack.test.ExampleInterface,false\n          slack.test.ExampleInterface,false\n          slack.test.ExampleInterface,false\n          slack.test.ExampleInterface,false\n          slack.test.ExampleInterface,false\n          slack.test.ExampleInterface,false\n          slack.test.ExampleInterface,false\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n          slack.test.TestClass,true\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/ObjectClassMockDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass ObjectClassMockDetectorTest : BaseSlackLintTest() {\n\n  private val testClass =\n    kotlin(\n        \"\"\"\n      package slack.test\n\n      object TestClass\n    \"\"\"\n      )\n      .indented()\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import org.mockito.Mock\n          import org.mockito.Spy\n          import slack.test.mockito.mock\n\n          class MyTests {\n            @Mock lateinit var fieldMock: TestClass\n            @Spy lateinit var fieldSpy: TestClass\n\n            fun example() {\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n              val localMock2 = mock<TestClass>()\n              val classRef = TestClass::class.java\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n\n              val dynamicMock = mock<TestClass> {\n\n              }\n              val assigned: TestClass = mock()\n              val fake = TestClass(\"this is fine\")\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), testClass, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.kt:8: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            @Mock lateinit var fieldMock: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:9: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            @Spy lateinit var fieldSpy: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:12: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:13: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:14: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              val localMock2 = mock<TestClass>()\n                               ~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:16: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:18: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              val dynamicMock = mock<TestClass> {\n                                ^\n          test/test/slack/test/TestClass.kt:21: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              val assigned: TestClass = mock()\n                                        ~~~~~~\n          8 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"test/test/slack/test/TestClass.java\",\n          \"\"\"\n          package slack.test;\n\n          import org.mockito.Mock;\n          import org.mockito.Spy;\n          import static org.mockito.Mockito.mock;\n          import static org.mockito.Mockito.spy;\n\n          class MyTests {\n            @Mock TestClass fieldMock;\n            @Spy TestClass fieldSpy;\n\n            public void example() {\n              TestClass localMock = mock(TestClass.class);\n              TestClass localSpy = spy(localMock);\n              Class<TestClass> classRef = TestClass.class;\n              TestClass localMock2 = mock(classRef);\n              TestClass fake = new TestClass(\"this is fine\");\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), testClass, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.java:9: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            @Mock TestClass fieldMock;\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:10: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n            @Spy TestClass fieldSpy;\n            ~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:13: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              TestClass localMock = mock(TestClass.class);\n                                    ~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:14: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              TestClass localSpy = spy(localMock);\n                                   ~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:16: Error: 'slack.test.TestClass' is an object, so mocking it should not be necessary [DoNotMockObjectClass]\n              TestClass localMock2 = mock(classRef);\n                                     ~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/PlatformTypeMockDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass PlatformTypeMockDetectorTest : BaseSlackLintTest() {\n\n  private val stubs =\n    arrayOf(\n      kotlin(\n        \"\"\"\n            package androidx.collection\n\n            class ArrayMap<K, V>\n        \"\"\"\n          .trimIndent()\n      )\n    )\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import slack.test.mockito.mock\n          import java.lang.Runnable\n          import kotlin.io.FileTreeWalk\n          import android.graphics.Typeface\n          import androidx.collection.ArrayMap\n\n          class MyTests {\n            fun example() {\n              // java.\n              mock<Comparable<String>>()\n              mock<Runnable>()\n              // kotlin.\n              mock<FileTreeWalk>()\n              mock<Lazy<String>>()\n              // android.\n              mock<Typeface>()\n              // androidx.\n              mock<ArrayMap<String, String>>()\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), *stubs, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.kt:12: Error: platform type 'java.lang.Comparable' should not be mocked [DoNotMockPlatformTypes]\n              mock<Comparable<String>>()\n              ~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:13: Error: platform type 'java.lang.Runnable' should not be mocked [DoNotMockPlatformTypes]\n              mock<Runnable>()\n              ~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:15: Error: platform type 'kotlin.io.FileTreeWalk' should not be mocked [DoNotMockPlatformTypes]\n              mock<FileTreeWalk>()\n              ~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:16: Error: platform type 'kotlin.Lazy' should not be mocked [DoNotMockPlatformTypes]\n              mock<Lazy<String>>()\n              ~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:18: Error: platform type 'android.graphics.Typeface' should not be mocked [DoNotMockPlatformTypes]\n              mock<Typeface>()\n              ~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:20: Error: platform type 'androidx.collection.ArrayMap' should not be mocked [DoNotMockPlatformTypes]\n              mock<ArrayMap<String, String>>()\n              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          6 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"test/test/slack/test/TestClass.java\",\n          \"\"\"\n          package slack.test;\n\n          import static org.mockito.Mockito.mock;\n          import java.lang.Runnable;\n          import kotlin.Lazy;\n          import kotlin.io.FileTreeWalk;\n          import android.graphics.Typeface;\n          import androidx.collection.ArrayMap;\n\n          class MyTests {\n            public void example() {\n              // java.\n              mock(Comparable.class);\n              mock(Runnable.class);\n              // kotlin.\n              mock(FileTreeWalk.class);\n              mock(Lazy.class);\n              // android.\n              mock(Typeface.class);\n              // androidx.\n              mock(ArrayMap.class);\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), *stubs, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.java:13: Error: platform type 'java.lang.Comparable' should not be mocked [DoNotMockPlatformTypes]\n              mock(Comparable.class);\n              ~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:14: Error: platform type 'java.lang.Runnable' should not be mocked [DoNotMockPlatformTypes]\n              mock(Runnable.class);\n              ~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:16: Error: platform type 'kotlin.io.FileTreeWalk' should not be mocked [DoNotMockPlatformTypes]\n              mock(FileTreeWalk.class);\n              ~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:17: Error: platform type 'kotlin.Lazy' should not be mocked [DoNotMockPlatformTypes]\n              mock(Lazy.class);\n              ~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:19: Error: platform type 'android.graphics.Typeface' should not be mocked [DoNotMockPlatformTypes]\n              mock(Typeface.class);\n              ~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:21: Error: platform type 'androidx.collection.ArrayMap' should not be mocked [DoNotMockPlatformTypes]\n              mock(ArrayMap.class);\n              ~~~~~~~~~~~~~~~~~~~~\n          6 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/RecordClassMockDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass RecordClassMockDetectorTest : BaseSlackLintTest() {\n\n  private val testClass =\n    kotlin(\n        \"\"\"\n      package slack.test\n\n      import kotlin.jvm.JvmRecord\n\n      @JvmRecord\n      data class TestClass(val foo: String)\n    \"\"\"\n      )\n      .indented()\n\n  private val testJavaClass =\n    java(\n        \"test/slack/test/TestJavaClass.java\",\n        \"\"\"\n      package slack.test;\n\n      record TestJavaClass(String foo) {\n\n      }\n    \"\"\",\n      )\n      .indented()\n\n  private val testClasses =\n    arrayOf(\n      testClass\n      // TODO test once lint supports java record\n      //  https://issuetracker.google.com/issues/283693337\n      //            testJavaClass\n    )\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import org.mockito.Mock\n          import org.mockito.Spy\n          import slack.test.mockito.mock\n\n          class MyTests {\n            @Mock lateinit var fieldMock: TestClass\n            @Spy lateinit var fieldSpy: TestClass\n\n            fun example() {\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n              val localMock2 = mock<TestClass>()\n              val classRef = TestClass::class.java\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n\n              val dynamicMock = mock<TestClass> {\n\n              }\n              val assigned: TestClass = mock()\n              val fake = TestClass(\"this is fine\")\n            }\n\n              // TODO uncomment once above Java record support is fixed\n//            @Mock lateinit var fieldMock2: TestJavaClass\n//            @Spy lateinit var fieldSpy2: TestJavaClass\n//\n//            fun example2() {\n//              val localMock1 = org.mockito.Mockito.mock(TestJavaClass::class.java)\n//              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n//              val localMock2 = mock<TestJavaClass>()\n//              val classRef = TestJavaClass::class.java\n//              val localMock3 = org.mockito.Mockito.mock(classRef)\n//\n//              val dynamicMock = mock<TestJavaClass> {\n//\n//              }\n//              val assigned: TestJavaClass = mock()\n//              val fake = TestJavaClass(\"this is fine\")\n//            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), *testClasses, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.kt:8: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                      @Mock lateinit var fieldMock: TestClass\n                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:9: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                      @Spy lateinit var fieldSpy: TestClass\n                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:12: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                        val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:13: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                        val localSpy1 = org.mockito.Mockito.spy(localMock1)\n                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:14: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                        val localMock2 = mock<TestClass>()\n                                         ~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:16: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                        val localMock3 = org.mockito.Mockito.mock(classRef)\n                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:18: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                        val dynamicMock = mock<TestClass> {\n                                          ^\n          test/test/slack/test/TestClass.kt:21: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                        val assigned: TestClass = mock()\n                                                  ~~~~~~\n          8 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"test/test/slack/test/TestClass.java\",\n          \"\"\"\n          package slack.test;\n\n          import org.mockito.Mock;\n          import org.mockito.Spy;\n          import static org.mockito.Mockito.mock;\n          import static org.mockito.Mockito.spy;\n\n          class MyTests {\n            @Mock TestClass fieldMock;\n            @Spy TestClass fieldSpy;\n\n            public void example() {\n              TestClass localMock = mock(TestClass.class);\n              TestClass localSpy = spy(localMock);\n              Class<TestClass> classRef = TestClass.class;\n              TestClass localMock2 = mock(classRef);\n              TestClass fake = new TestClass(\"this is fine\");\n            }\n\n              // TODO uncomment once above Java record support is fixed\n//            @Mock TestJavaClass fieldMock2;\n//            @Spy TestJavaClass fieldSpy2;\n//\n//            public void example2() {\n//              TestJavaClass localMock = mock(TestJavaClass.class);\n//              TestJavaClass localSpy = spy(localMock);\n//              Class<TestJavaClass> classRef = TestJavaClass.class;\n//              TestJavaClass localMock2 = mock(classRef);\n//              TestJavaClass fake = new TestJavaClass(\"this is fine\");\n//            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), *testClasses, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.java:9: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                      @Mock TestClass fieldMock;\n                      ~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:10: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                      @Spy TestClass fieldSpy;\n                      ~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:13: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                        TestClass localMock = mock(TestClass.class);\n                                              ~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:14: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                        TestClass localSpy = spy(localMock);\n                                             ~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:16: Error: 'slack.test.TestClass' is a record class, so mocking it should not be necessary [DoNotMockRecordClass]\n                        TestClass localMock2 = mock(classRef);\n                                               ~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/SealedClassMockDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport com.intellij.pom.java.LanguageLevel\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass SealedClassMockDetectorTest : BaseSlackLintTest() {\n\n  private val testClass =\n    kotlin(\n        \"\"\"\n      package slack.test\n\n      sealed class TestClass\n    \"\"\"\n      )\n      .indented()\n\n  private val javaTestClass =\n    java(\n        \"\"\"\n      package slack.test;\n\n      sealed interface TestJavaClass permits TestJavaClass.Subtype1 {\n        interface Subtype1 {\n\n        }\n      }\n    \"\"\"\n      )\n      .indented()\n\n  private val testClasses = arrayOf(testClass, javaTestClass)\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  override val skipTestModes: Array<TestMode>\n    // I don't understand the point of this mode\n    get() = arrayOf(TestMode.SUPPRESSIBLE)\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import org.mockito.Mock\n          import org.mockito.Spy\n          import slack.test.mockito.mock\n\n          class MyTests {\n            @Mock lateinit var fieldMock: TestClass\n            @Spy lateinit var fieldSpy: TestClass\n\n            fun example() {\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n              val localMock2 = mock<TestClass>()\n              val classRef = TestClass::class.java\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n\n              val dynamicMock = mock<TestClass> {\n\n              }\n              val assigned: TestClass = mock()\n              val fake = TestClass(\"this is fine\")\n            }\n\n            @Mock lateinit var fieldMock2: TestJavaClass\n            @Spy lateinit var fieldSpy2: TestJavaClass\n            fun example2() {\n              val localMock1 = org.mockito.Mockito.mock(TestJavaClass::class.java)\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n              val localMock2 = mock<TestJavaClass>()\n              val classRef = TestJavaClass::class.java\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n\n              val dynamicMock = mock<TestJavaClass> {\n\n              }\n              val assigned: TestJavaClass = mock()\n              val fake = TestJavaClass(\"this is fine\")\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .javaLanguageLevel(LanguageLevel.JDK_17)\n      .files(*mockFileStubs(), *testClasses, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.kt:8: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            @Mock lateinit var fieldMock: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:9: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            @Spy lateinit var fieldSpy: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:12: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:13: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:14: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val localMock2 = mock<TestClass>()\n                               ~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:16: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:18: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val dynamicMock = mock<TestClass> {\n                                ^\n          test/test/slack/test/TestClass.kt:21: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val assigned: TestClass = mock()\n                                        ~~~~~~\n          test/test/slack/test/TestClass.kt:25: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            @Mock lateinit var fieldMock2: TestJavaClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:26: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            @Spy lateinit var fieldSpy2: TestJavaClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:28: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val localMock1 = org.mockito.Mockito.mock(TestJavaClass::class.java)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:29: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:30: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val localMock2 = mock<TestJavaClass>()\n                               ~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:32: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:34: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val dynamicMock = mock<TestJavaClass> {\n                                ^\n          test/test/slack/test/TestClass.kt:37: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              val assigned: TestJavaClass = mock()\n                                            ~~~~~~\n          16 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"test/test/slack/test/TestClass.java\",\n          \"\"\"\n          package slack.test;\n\n          import org.mockito.Mock;\n          import org.mockito.Spy;\n          import static org.mockito.Mockito.mock;\n          import static org.mockito.Mockito.spy;\n\n          class MyTests {\n            @Mock TestClass fieldMock;\n            @Spy TestClass fieldSpy;\n\n            public void example() {\n              TestClass localMock = mock(TestClass.class);\n              TestClass localSpy = spy(localMock);\n              Class<TestClass> classRef = TestClass.class;\n              TestClass localMock2 = mock(classRef);\n              TestClass fake = new TestClass(\"this is fine\");\n            }\n\n            @Mock TestJavaClass fieldMock2;\n            @Spy TestJavaClass fieldSpy2;\n            public void example2() {\n              TestClass localMock = mock(TestJavaClass.class);\n              TestJavaClass localSpy = spy(localMock);\n              Class<TestJavaClass> classRef = TestJavaClass.class;\n              TestJavaClass localMock2 = mock(classRef);\n              TestJavaClass fake = new TestJavaClass(\"this is fine\");\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .javaLanguageLevel(LanguageLevel.JDK_17)\n      .files(*mockFileStubs(), *testClasses, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.java:9: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            @Mock TestClass fieldMock;\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:10: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            @Spy TestClass fieldSpy;\n            ~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:13: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              TestClass localMock = mock(TestClass.class);\n                                    ~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:14: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              TestClass localSpy = spy(localMock);\n                                   ~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:16: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              TestClass localMock2 = mock(classRef);\n                                     ~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:20: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            @Mock TestJavaClass fieldMock2;\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:21: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n            @Spy TestJavaClass fieldSpy2;\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:23: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              TestClass localMock = mock(TestJavaClass.class);\n                                    ~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:24: Error: 'slack.test.TestClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              TestJavaClass localSpy = spy(localMock);\n                                       ~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:26: Error: 'slack.test.TestJavaClass' is a sealed type and has a restricted type hierarchy, use a subtype instead. [DoNotMockSealedClass]\n              TestJavaClass localMock2 = mock(classRef);\n                                         ~~~~~~~~~~~~~~\n          10 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/mocking/ValueClassMockDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.mocking\n\nimport org.junit.Ignore\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\n@Ignore(\"https://issuetracker.google.com/issues/283715187\")\nclass ValueClassMockDetectorTest : BaseSlackLintTest() {\n\n  private val testClass =\n    kotlin(\n        \"\"\"\n      package slack.test\n\n      import kotlin.jvm.JvmInline\n\n      @JvmInline\n      value class TestClass(val foo: String)\n    \"\"\"\n      )\n      .indented()\n\n  override fun getDetector() = MockDetector()\n\n  override fun getIssues() = MockDetector.ISSUES.toList()\n\n  @Test\n  fun kotlinTests() {\n    val source =\n      kotlin(\n          \"test/test/slack/test/TestClass.kt\",\n          \"\"\"\n          package slack.test\n\n          import org.mockito.Mock\n          import org.mockito.Spy\n          import slack.test.mockito.mock\n\n          class MyTests {\n            @Mock lateinit var fieldMock: TestClass\n            @Spy lateinit var fieldSpy: TestClass\n\n            fun example() {\n//              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n              val localSpy1 = org.mockito.Mockito.spy(1u)\n//              val localMock2 = mock<TestClass>()\n//              val classRef = TestClass::class.java\n//              val localMock3 = org.mockito.Mockito.mock(classRef)\n\n//              val dynamicMock = mock<TestClass> {\n//\n//              }\n//              val assigned: TestClass = mock()\n//              val fake = TestClass(\"this is fine\")\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), testClass, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.kt:8: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n            @Mock lateinit var fieldMock: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:9: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n            @Spy lateinit var fieldSpy: TestClass\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:12: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n              val localMock1 = org.mockito.Mockito.mock(TestClass::class.java)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:13: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n              val localSpy1 = org.mockito.Mockito.spy(localMock1)\n                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:14: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n              val localMock2 = mock<TestClass>()\n                               ~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:16: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n              val localMock3 = org.mockito.Mockito.mock(classRef)\n                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.kt:18: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n              val dynamicMock = mock<TestClass> {\n                                ^\n          test/test/slack/test/TestClass.kt:21: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n              val assigned: TestClass = mock()\n              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          8 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun javaTests() {\n    val source =\n      java(\n          \"test/test/slack/test/TestClass.java\",\n          \"\"\"\n          package slack.test;\n\n          import org.mockito.Mock;\n          import org.mockito.Spy;\n          import static org.mockito.Mockito.mock;\n          import static org.mockito.Mockito.spy;\n\n          class MyTests {\n            @Mock TestClass fieldMock;\n            @Spy TestClass fieldSpy;\n\n            public void example() {\n              TestClass localMock = mock(TestClass.class);\n              TestClass localSpy = spy(localMock);\n              Class<TestClass> classRef = TestClass.class;\n              TestClass localMock2 = mock(classRef);\n              TestClass fake = new TestClass(\"this is fine\");\n            }\n          }\n        \"\"\",\n        )\n        .indented()\n\n    lint()\n      .files(*mockFileStubs(), testClass, source)\n      .run()\n      .expect(\n        \"\"\"\n          test/test/slack/test/TestClass.java:9: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n            @Mock TestClass fieldMock;\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:10: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n            @Spy TestClass fieldSpy;\n            ~~~~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:13: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n              TestClass localMock = mock(TestClass.class);\n                                    ~~~~~~~~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:14: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n              TestClass localSpy = spy(localMock);\n                                   ~~~~~~~~~~~~~~\n          test/test/slack/test/TestClass.java:16: Error: value classes represent inlined types, so mocking them should not be necessary [DoNotMockValueClass]\n              TestClass localMock2 = mock(classRef);\n                                     ~~~~~~~~~~~~~~\n          5 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/parcel/ParcelizeFunctionPropertyDetectorTest.kt",
    "content": "// Copyright (C) 2023 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.parcel\n\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass ParcelizeFunctionPropertyDetectorTest : BaseSlackLintTest() {\n\n  companion object {\n    private val PARCELIZE_STUBS =\n      kotlin(\n          \"test/kotlinx/parcelize/Parcelize.kt\",\n          \"\"\"\n        package kotlinx.parcelize\n\n        annotation class Parcelize\n        annotation class IgnoredOnParcel\n      \"\"\",\n        )\n        .indented()\n  }\n\n  override fun getDetector() = ParcelizeFunctionPropertyDetector()\n\n  override fun getIssues() = listOf(ParcelizeFunctionPropertyDetector.ISSUE)\n\n  @Test\n  fun simple() {\n    lint()\n      .files(\n        PARCELIZE_STUBS,\n        kotlin(\n            \"\"\"\n          package test.pkg\n\n          import android.os.Parcelable\n          import kotlinx.parcelize.Parcelize\n          import kotlinx.parcelize.IgnoredOnParcel\n\n          typealias FunctionType = () -> String\n\n          @Parcelize\n          class Example1(\n            val foo: String,\n            val functionType1: () -> String,\n            val functionType2: (String) -> String,\n            val functionType3: String.() -> String,\n            val functionType4: () -> Unit,\n            val functionType5: suspend () -> Unit,\n            val aliasedFunction: FunctionType,\n            val functionClass: FunctionClass,\n            @IgnoredOnParcel\n            val ignoredFunction: () -> Unit = {}, // This is allowed\n          ) : Parcelable\n          \"\"\"\n          )\n          .indented(),\n      )\n      .allowCompilationErrors(false)\n      .run()\n      .expect(\n        \"\"\"\n        src/test/pkg/Example1.kt:12: Error: While technically (and surprisingly) supported by Parcelize, function types should not be used in Parcelize classes. There are only limited conditions where it will work and it's usually a sign that you're modeling your data wrong. [ParcelizeFunctionProperty]\n          val functionType1: () -> String,\n                             ~~~~~~~~~~~~\n        src/test/pkg/Example1.kt:13: Error: While technically (and surprisingly) supported by Parcelize, function types should not be used in Parcelize classes. There are only limited conditions where it will work and it's usually a sign that you're modeling your data wrong. [ParcelizeFunctionProperty]\n          val functionType2: (String) -> String,\n                             ~~~~~~~~~~~~~~~~~~\n        src/test/pkg/Example1.kt:14: Error: While technically (and surprisingly) supported by Parcelize, function types should not be used in Parcelize classes. There are only limited conditions where it will work and it's usually a sign that you're modeling your data wrong. [ParcelizeFunctionProperty]\n          val functionType3: String.() -> String,\n                             ~~~~~~~~~~~~~~~~~~~\n        src/test/pkg/Example1.kt:15: Error: While technically (and surprisingly) supported by Parcelize, function types should not be used in Parcelize classes. There are only limited conditions where it will work and it's usually a sign that you're modeling your data wrong. [ParcelizeFunctionProperty]\n          val functionType4: () -> Unit,\n                             ~~~~~~~~~~\n        src/test/pkg/Example1.kt:16: Error: While technically (and surprisingly) supported by Parcelize, function types should not be used in Parcelize classes. There are only limited conditions where it will work and it's usually a sign that you're modeling your data wrong. [ParcelizeFunctionProperty]\n          val functionType5: suspend () -> Unit,\n                             ~~~~~~~~~~~~~~~~~~\n        src/test/pkg/Example1.kt:17: Error: While technically (and surprisingly) supported by Parcelize, function types should not be used in Parcelize classes. There are only limited conditions where it will work and it's usually a sign that you're modeling your data wrong. [ParcelizeFunctionProperty]\n          val aliasedFunction: FunctionType,\n                               ~~~~~~~~~~~~\n        6 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/resources/FullyQualifiedResourceDetectorTest.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.resources\n\nimport com.android.tools.lint.checks.infrastructure.TestLintTask\nimport com.android.tools.lint.detector.api.Detector\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\nimport slack.lint.resources.ImportAliasesLoader.IMPORT_ALIASES\n\nclass FullyQualifiedResourceDetectorTest : BaseSlackLintTest() {\n\n  override fun getDetector(): Detector = FullyQualifiedResourceDetector()\n\n  override fun getIssues() = listOf(FullyQualifiedResourceDetector.ISSUE)\n\n  override fun lint(): TestLintTask {\n    return super.lint()\n      .configureOption(\n        IMPORT_ALIASES,\n        \"slack.l10n.R as L10nR, slack.uikit.resources.R as SlackKitR, slack.uikit.R as UiKitR\",\n      )\n  }\n\n  @Test\n  fun `test success`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.l10n.R as L10nR\n\n          class MyClass {\n\n             init {\n                  val appName = getString(L10R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `test failure no imports`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          class MyClass {\n\n             init {\n                  val appName = getString(slack.l10n.R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:6: Error: Use L10nR as an import alias instead [FullyQualifiedResource]\n                val appName = getString(slack.l10n.R.string.app_name)\n                                        ~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/pkg/subpackage/MyClass.kt line 6: Replace with import alias:\n        @@ -3 +3\n        + import slack.l10n.R as L10nR\n        +\n        @@ -6 +8\n        -         val appName = getString(slack.l10n.R.string.app_name)\n        +         val appName = getString(L10nR.string.app_name)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure no import`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.pkg.R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(slack.l10n.R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:8: Error: Use L10nR as an import alias instead [FullyQualifiedResource]\n                val appName = getString(slack.l10n.R.string.app_name)\n                                        ~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/pkg/subpackage/MyClass.kt line 8: Replace with import alias:\n        @@ -4 +4\n        + import slack.l10n.R as L10nR\n        @@ -8 +9\n        -         val appName = getString(slack.l10n.R.string.app_name)\n        +         val appName = getString(L10nR.string.app_name)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure import`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.pkg.R\n          import slack.l10n.R as L10nR\n\n          class MyClass {\n\n             init {\n                  val appName = getString(slack.l10n.R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:9: Error: Use L10nR as an import alias instead [FullyQualifiedResource]\n                val appName = getString(slack.l10n.R.string.app_name)\n                                        ~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/pkg/subpackage/MyClass.kt line 9: Replace with import alias:\n        @@ -9 +9\n        -         val appName = getString(slack.l10n.R.string.app_name)\n        +         val appName = getString(L10nR.string.app_name)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure import without alias`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.l10n.R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(slack.l10n.R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:8: Error: Use L10nR as an import alias instead [FullyQualifiedResource]\n                val appName = getString(slack.l10n.R.string.app_name)\n                                        ~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/pkg/subpackage/MyClass.kt line 8: Replace with import alias:\n        @@ -4 +4\n        + import slack.l10n.R as L10nR\n        @@ -8 +9\n        -         val appName = getString(slack.l10n.R.string.app_name)\n        +         val appName = getString(L10nR.string.app_name)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure import wrong alias`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.l10n.R as L10R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(slack.l10n.R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:8: Error: Use L10nR as an import alias instead [FullyQualifiedResource]\n                val appName = getString(slack.l10n.R.string.app_name)\n                                        ~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/pkg/subpackage/MyClass.kt line 8: Replace with import alias:\n        @@ -4 +4\n        + import slack.l10n.R as L10nR\n        @@ -8 +9\n        -         val appName = getString(slack.l10n.R.string.app_name)\n        +         val appName = getString(L10nR.string.app_name)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure no fix`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.l10n.R as L10R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(slack.pkg.R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:8: Error: Use an import alias instead [FullyQualifiedResource]\n                val appName = getString(slack.pkg.R.string.app_name)\n                                        ~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\"\")\n  }\n\n  @Test\n  fun `test java no-op`() {\n    lint()\n      .files(\n        java(\n            \"\"\"\n          package slack.pkg.subpackage;\n\n          class MyClass {\n            MyClass(){\n            String appName = getString(slack.l10n.R.string.app_name);\n           }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/resources/MissingResourceImportAliasDetectorTest.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.resources\n\nimport com.android.tools.lint.checks.infrastructure.TestLintTask\nimport com.android.tools.lint.detector.api.Detector\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass MissingResourceImportAliasDetectorTest : BaseSlackLintTest() {\n\n  override fun getDetector(): Detector = MissingResourceImportAliasDetector()\n\n  override fun getIssues() = listOf(MissingResourceImportAliasDetector.ISSUE)\n\n  override fun lint(): TestLintTask {\n    return super.lint()\n      .configureOption(\n        ImportAliasesLoader.IMPORT_ALIASES,\n        \"slack.l10n.R as L10nR, slack.uikit.resources.R as SlackKitR, slack.uikit.R as UiKitR\",\n      )\n  }\n\n  @Test\n  fun `test success`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package lint.test.pkg.subpackage\n\n          import slack.l10n.R as L10nR\n          import lint.test.pkg.R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(L10nR.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `test failure no references`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package lint.test.pkg\n\n          import slack.l10n.R\n\n          class MyClass\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/lint/test/pkg/MyClass.kt:3: Error: Use an import alias for R classes from other modules [MissingResourceImportAlias]\n        import slack.l10n.R\n        ~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/lint/test/pkg/MyClass.kt line 3: Add import alias:\n        @@ -3 +3\n        - import slack.l10n.R\n        + import slack.l10n.R as L10nR\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure one reference`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package lint.test.pkg\n\n          import slack.l10n.R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/lint/test/pkg/MyClass.kt:3: Error: Use an import alias for R classes from other modules [MissingResourceImportAlias]\n        import slack.l10n.R\n        ~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/lint/test/pkg/MyClass.kt line 3: Add import alias:\n        @@ -3 +3\n        - import slack.l10n.R\n        + import slack.l10n.R as L10nR\n        @@ -8 +8\n        -         val appName = getString(R.string.app_name)\n        +         val appName = getString(L10nR.string.app_name)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure multiple references`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package lint.test.pkg\n\n          import slack.l10n.R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(R.string.app_name) + \"-\" + getString(R.string.suffix)\n                  getColor(R.color.transparent).let { println(it) }\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/lint/test/pkg/MyClass.kt:3: Error: Use an import alias for R classes from other modules [MissingResourceImportAlias]\n        import slack.l10n.R\n        ~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/lint/test/pkg/MyClass.kt line 3: Add import alias:\n        @@ -3 +3\n        - import slack.l10n.R\n        + import slack.l10n.R as L10nR\n        @@ -8 +8\n        -         val appName = getString(R.string.app_name) + \"-\" + getString(R.string.suffix)\n        -         getColor(R.color.transparent).let { println(it) }\n        +         val appName = getString(L10nR.string.app_name) + \"-\" + getString(L10nR.string.suffix)\n        +         getColor(L10nR.color.transparent).let { println(it) }\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure VERSION_CODES R reference`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package lint.test.pkg\n\n          import slack.l10n.R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(R.string.app_name) + \"-\" + getString(R.string.suffix)\n                  if (isAtLeastApi(Build.VERSION_CODES.R)) {\n                        getColor(R.color.transparent).let { println(it) }\n                  }\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/lint/test/pkg/MyClass.kt:3: Error: Use an import alias for R classes from other modules [MissingResourceImportAlias]\n        import slack.l10n.R\n        ~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/lint/test/pkg/MyClass.kt line 3: Add import alias:\n        @@ -3 +3\n        - import slack.l10n.R\n        + import slack.l10n.R as L10nR\n        @@ -8 +8\n        -         val appName = getString(R.string.app_name) + \"-\" + getString(R.string.suffix)\n        +         val appName = getString(L10nR.string.app_name) + \"-\" + getString(L10nR.string.suffix)\n        @@ -10 +10\n        -               getColor(R.color.transparent).let { println(it) }\n        +               getColor(L10nR.color.transparent).let { println(it) }\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure no fix`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package lint.test.pkg\n\n          import lint.test.subpkg.R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/lint/test/pkg/MyClass.kt:3: Error: Use an import alias for R classes from other modules [MissingResourceImportAlias]\n        import lint.test.subpkg.R\n        ~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\"\")\n  }\n\n  @Test\n  fun `test java no-op`() {\n    lint()\n      .files(\n        java(\n            \"\"\"\n          package lint.test.pkg;\n\n          import lint.test.subpkg.R;\n\n          class MyClass {\n\n             MyClass() {\n                  String appName = getString(R.string.app_name);\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/resources/WrongResourceImportAliasDetectorTest.kt",
    "content": "// Copyright (C) 2022 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.resources\n\nimport com.android.tools.lint.checks.infrastructure.TestLintTask\nimport com.android.tools.lint.detector.api.Detector\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass WrongResourceImportAliasDetectorTest : BaseSlackLintTest() {\n\n  override fun getDetector(): Detector = WrongResourceImportAliasDetector()\n\n  override fun getIssues() = listOf(WrongResourceImportAliasDetector.ISSUE)\n\n  override fun lint(): TestLintTask {\n    return super.lint()\n      .configureOption(\n        ImportAliasesLoader.IMPORT_ALIASES,\n        \"slack.l10n.R as L10nR, slack.uikit.resources.R as SlackKitR, slack.uikit.R as UiKitR\",\n      )\n  }\n\n  @Test\n  fun `test success`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.l10n.R as L10nR\n          import slack.pkg.R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(L10R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `test failure no references`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.l10n.R as L10R\n\n          class MyClass\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:3: Error: Use L10nR as an import alias here [WrongResourceImportAlias]\n        import slack.l10n.R as L10R\n        ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/pkg/subpackage/MyClass.kt line 3: Replace import alias:\n        @@ -3 +3\n        - import slack.l10n.R as L10R\n        + import slack.l10n.R as L10nR\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure one reference`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.uikit.resources.R as SlackKitR\n          import slack.l10n.R as L10R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(L10R.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:4: Error: Use L10nR as an import alias here [WrongResourceImportAlias]\n        import slack.l10n.R as L10R\n        ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/pkg/subpackage/MyClass.kt line 4: Replace import alias:\n        @@ -4 +4\n        - import slack.l10n.R as L10R\n        + import slack.l10n.R as L10nR\n        @@ -9 +9\n        -         val appName = getString(L10R.string.app_name)\n        +         val appName = getString(L10nR.string.app_name)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure multiple references`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.l10n.R as L10R\n\n          class MyClass {\n\n             init {\n                  val appName = getString(L10R.string.app_name) + \"-\" + getString(L10R.string.suffix)\n                  getColor(R.color.transparent).let { println(it) }\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:3: Error: Use L10nR as an import alias here [WrongResourceImportAlias]\n        import slack.l10n.R as L10R\n        ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/pkg/subpackage/MyClass.kt line 3: Replace import alias:\n        @@ -3 +3\n        - import slack.l10n.R as L10R\n        + import slack.l10n.R as L10nR\n        @@ -8 +8\n        -         val appName = getString(L10R.string.app_name) + \"-\" + getString(L10R.string.suffix)\n        +         val appName = getString(L10nR.string.app_name) + \"-\" + getString(L10nR.string.suffix)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test failure multiple wrong imports`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.uikit.resources.R as SKR\n          import slack.l10n.R as L10R\n\n          class MyClass {\n\n             init {\n                  getColor(SKR.color.transparent).let { println(it) }\n                  val appName = getString(L10R.string.app_name) + \"-\" + getString(L10R.string.suffix)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/slack/pkg/subpackage/MyClass.kt:3: Error: Use SlackKitR as an import alias here [WrongResourceImportAlias]\n        import slack.uikit.resources.R as SKR\n        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/slack/pkg/subpackage/MyClass.kt line 3: Replace import alias:\n        @@ -3 +3\n        - import slack.uikit.resources.R as SKR\n        + import slack.uikit.resources.R as SlackKitR\n        @@ -9 +9\n        -         getColor(SKR.color.transparent).let { println(it) }\n        +         getColor(SlackKitR.color.transparent).let { println(it) }\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `test no fix`() {\n    lint()\n      .files(\n        kotlin(\n            \"\"\"\n          package slack.pkg.subpackage\n\n          import slack.pkg.subpkg.R as SubPkgR\n\n          class MyClass {\n\n             init {\n                  val appName = getString(SubPkgR.string.app_name)\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `test java no-op`() {\n    lint()\n      .files(\n        java(\n            \"\"\"\n          package slack.pkg.subpackage;\n\n          import slack.l10n.R;\n\n          class MyClass {\n\n             MyClass() {\n                  String appName = getString(R.string.app_name);\n              }\n\n           }\n          \"\"\"\n          )\n          .indented()\n      )\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/retrofit/RetrofitJarLoader.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.retrofit\n\nimport com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile\nimport java.io.File\nimport slack.lint.BaseSlackLintTest\n\n/** Loads the test Retrofit 3 jar from resources. */\nfun BaseSlackLintTest.retrofit3Jar() =\n  LibraryReferenceTestFile(File(javaClass.classLoader.getResource(\"retrofit-3.0.0.jar\")!!.toURI()))\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/retrofit/RetrofitUsageDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.retrofit\n\nimport com.android.tools.lint.checks.infrastructure.TestFile\nimport com.android.tools.lint.detector.api.Detector\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass RetrofitUsageDetectorTest : BaseSlackLintTest() {\n\n  private companion object {\n    val allowUnitResult: TestFile =\n      kotlin(\n          \"\"\"\n        package slack.lint.annotations\n\n        @Target(AnnotationTarget.FUNCTION)\n        @Retention(AnnotationRetention.SOURCE)\n        annotation class AllowUnitResult\n      \"\"\"\n        )\n        .indented()\n  }\n\n  private val retrofit3Jar = retrofit3Jar()\n\n  override fun getDetector(): Detector = RetrofitUsageDetector()\n\n  override fun getIssues() = listOf(RetrofitUsageDetector.ISSUE)\n\n  @Test\n  fun formEncoding() {\n    lint()\n      .files(\n        retrofit3Jar,\n        kotlin(\n            \"\"\"\n            package test\n\n            import retrofit2.http.Field\n            import retrofit2.http.FormUrlEncoded\n            import retrofit2.http.GET\n            import retrofit2.http.POST\n\n            interface Example {\n              @GET(\"/\")\n              @FormUrlEncoded\n              fun wrongMethod(): String\n\n              @POST(\"/\")\n              @FormUrlEncoded\n              fun missingFieldParams(): String\n\n              @POST(\"/\")\n              fun missingAnnotation(@Field(\"hi\") input: String): String\n\n              @FormUrlEncoded\n              @POST(\"/\")\n              fun correct(@Field(\"hi\") input: String): String\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/Example.kt:11: Error: @FormUrlEncoded requires @PUT, @POST, or @PATCH. [RetrofitUsage]\n          fun wrongMethod(): String\n              ~~~~~~~~~~~\n        src/test/Example.kt:14: Error: @FormUrlEncoded but has no @Field(Map) parameters. [RetrofitUsage]\n          @FormUrlEncoded\n          ~~~~~~~~~~~~~~~\n        src/test/Example.kt:18: Error: @Field(Map) param requires @FormUrlEncoded. [RetrofitUsage]\n          fun missingAnnotation(@Field(\"hi\") input: String): String\n              ~~~~~~~~~~~~~~~~~\n        3 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n        Autofix for src/test/Example.kt line 14: Remove '@FormUrlEncoded':\n        @@ -14 +14\n        -   @FormUrlEncoded\n        Autofix for src/test/Example.kt line 18: Replace with @retrofit2.http.FormUrlEncoded...:\n        @@ -17 +17\n        -   @POST(\"/\")\n        +   @retrofit2.http.FormUrlEncoded\n        + @POST(\"/\")\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun bodies() {\n    lint()\n      .files(\n        retrofit3Jar,\n        kotlin(\n            \"\"\"\n            package test\n\n            import retrofit2.http.Body\n            import retrofit2.http.GET\n            import retrofit2.http.Multipart\n            import retrofit2.http.Part\n            import retrofit2.http.POST\n\n            interface Example {\n              @GET(\"/\")\n              fun wrongMethod(@Body body : String): String\n\n              @POST(\"/\")\n              fun missingBody(): String\n\n              @POST(\"/\")\n              fun doubleBody(@Body input: String, @Body input2: String): String\n\n              @POST(\"/\")\n              fun correct(@Body input: String): String\n\n              @Multipart\n              @POST(\"/\")\n              fun multipartCorrect(@Part input: String): String\n\n              @Multipart\n              @GET(\"/\")\n              fun multipartBadMethod(@Part input: String): String\n\n              @Multipart\n              @POST(\"/\")\n              fun multipartBadParameterType(@Body input: String): String\n\n              @Multipart\n              @POST(\"/\")\n              fun multipartMissingPartParameter(): String\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/Example.kt:10: Error: @Body param requires @PUT, @POST, or @PATCH. [RetrofitUsage]\n          @GET(\"/\")\n          ~~~~~~~~~\n        src/test/Example.kt:13: Error: This annotation requires an @Body parameter. [RetrofitUsage]\n          @POST(\"/\")\n          ~~~~~~~~~~\n        src/test/Example.kt:17: Error: Duplicate @Body param!. [RetrofitUsage]\n          fun doubleBody(@Body input: String, @Body input2: String): String\n                                              ~~~~~~~~~~~~~~~~~~~~\n        src/test/Example.kt:28: Error: @Multipart requires @PUT, @POST, or @PATCH. [RetrofitUsage]\n          fun multipartBadMethod(@Part input: String): String\n              ~~~~~~~~~~~~~~~~~~\n        src/test/Example.kt:31: Error: @Multipart methods should only contain @Part parameters. [RetrofitUsage]\n          @POST(\"/\")\n          ~~~~~~~~~~\n        src/test/Example.kt:35: Error: @Multipart methods should contain at least one @Part parameter. [RetrofitUsage]\n          @POST(\"/\")\n          ~~~~~~~~~~\n        6 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun unitReturn() {\n    lint()\n      .files(\n        retrofit3Jar,\n        allowUnitResult,\n        kotlin(\n            \"\"\"\n            package test\n\n            import retrofit2.http.GET\n            import slack.lint.annotations.AllowUnitResult\n\n            interface Example {\n              @GET(\"/\")\n              fun unitMethod()\n\n              @GET(\"/\")\n              suspend fun suspendUnitMethod()\n\n              @GET(\"/\")\n              fun unitMethodExplicit(): Unit\n\n              suspend fun suspendUnitMethodExplicit(): Unit\n\n              @AllowUnitResult\n              @PUT(\"/\")\n              suspend fun suspendUnitMethodAllowUnitResult()\n\n              @AllowUnitResult\n              @DELETE(\"/\")\n              suspend fun suspendUnitMethodExplicitAllowUnitResult(): Unit\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/Example.kt:8: Error: Retrofit endpoints should return something other than Unit/void. [RetrofitUsage]\n          fun unitMethod()\n              ~~~~~~~~~~\n        src/test/Example.kt:11: Error: Retrofit endpoints should return something other than Unit/void. [RetrofitUsage]\n          suspend fun suspendUnitMethod()\n                      ~~~~~~~~~~~~~~~~~\n        src/test/Example.kt:14: Error: Retrofit endpoints should return something other than Unit/void. [RetrofitUsage]\n          fun unitMethodExplicit(): Unit\n              ~~~~~~~~~~~~~~~~~~\n        3 errors\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/rx/RxJavaJarLoader.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.rx\n\nimport com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile\nimport java.io.File\nimport slack.lint.BaseSlackLintTest\n\n/** Loads the test RxJava 3 jar from resources. */\nfun BaseSlackLintTest.rxJavaJar3() =\n  LibraryReferenceTestFile(File(javaClass.classLoader.getResource(\"rxjava-3.1.0.jar\")!!.toURI()))\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/rx/RxObservableEmitDetectorTest.kt",
    "content": "// Copyright (C) 2025 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.rx\n\nimport com.android.tools.lint.checks.infrastructure.TestFile\nimport java.util.Locale.getDefault\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass RxObservableEmitDetectorTest : BaseSlackLintTest() {\n  override fun getDetector() = RxObservableEmitDetector()\n\n  override fun getIssues() = RxObservableEmitDetector.issues\n\n  @Test\n  fun testDocumentationExample() {\n    testWhenResultsOfSendAreReturned(RX_OBSERVABLE, SEND)\n  }\n\n  @Test\n  fun `rxObservable - does not call send or trySend`() {\n    lint()\n      .files(\n        *files,\n        kotlin(\n            \"\"\"\n            package test\n\n            import kotlinx.coroutines.rx3.rxObservable\n\n            class Foo {\n              fun foo() {\n                rxObservable { println(\"foo\") }\n              }\n            }\n            \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/Foo.kt:7: Hint: rxObservable does not call send() or trySend() [RxObservableDoesNotEmit]\n            rxObservable { println(\"foo\") }\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        0 errors, 0 warnings, 1 hint\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `rxFlowable - does not call send or trySend`() {\n    lint()\n      .files(\n        *files,\n        kotlin(\n            \"\"\"\n            package test\n\n            import kotlinx.coroutines.rx3.rxFlowable\n\n            class Foo {\n              fun foo() {\n                rxFlowable { println(\"foo\") }\n              }\n            }\n            \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n        src/test/Foo.kt:7: Hint: rxFlowable does not call send() or trySend() [RxFlowableDoesNotEmit]\n            rxFlowable { println(\"foo\") }\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n        0 errors, 0 warnings, 1 hint\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `rxObservable - calls send`() {\n    testWhenResultsOfSendAreReturned(RX_OBSERVABLE, SEND)\n  }\n\n  @Test\n  fun `rxObservable - calls trySend`() {\n    testWhenResultsOfSendAreReturned(RX_OBSERVABLE, TRY_SEND)\n  }\n\n  @Test\n  fun `rxFlowable - calls send`() {\n    testWhenResultsOfSendAreReturned(\"rxFlowable\", SEND)\n  }\n\n  @Test\n  fun `rxFlowable - calls trySend`() {\n    testWhenResultsOfSendAreReturned(\"rxFlowable\", TRY_SEND)\n  }\n\n  @Test\n  fun `rxObservable - results of send are not the lambda return value`() {\n    testWhenResultsOfSendAreNotReturned(RX_OBSERVABLE, SEND)\n  }\n\n  @Test\n  fun `rxObservable - results of trySend are not the lambda return value`() {\n    testWhenResultsOfSendAreNotReturned(RX_OBSERVABLE, TRY_SEND)\n  }\n\n  @Test\n  fun `rxFlowable - results of send are not the lambda return value`() {\n    testWhenResultsOfSendAreNotReturned(\"rxFlowable\", SEND)\n  }\n\n  @Test\n  fun `rxFlowable - results of trySend are not the lambda return value`() {\n    testWhenResultsOfSendAreNotReturned(\"rxFlowable\", TRY_SEND)\n  }\n\n  private fun testWhenResultsOfSendAreReturned(method: String, emitter: String) {\n    lint()\n      .files(\n        *files,\n        kotlin(\n            \"\"\"\n            package test\n\n            import kotlinx.coroutines.rx3.$method\n\n            class Foo {\n              fun foo() {\n                $method { $emitter(\"foo\") }\n              }\n            }\n            \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  private fun testWhenResultsOfSendAreNotReturned(method: String, emitter: String) {\n    lint()\n      .files(\n        *files,\n        kotlin(\n            \"\"\"\n            package test\n\n            import kotlinx.coroutines.rx3.$method\n\n            class Foo {\n              fun foo() {\n                $method {\n                  $emitter(\"foo\")\n                  println(\"bar\")\n                }\n              }\n            }\n            \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `rxObservable - should fail when outer factor does not call send`() {\n    testWhenOuterFactoryDoesNotCallSend(RX_OBSERVABLE, SEND)\n  }\n\n  @Test\n  fun `rxObservable - should fail when outer factory does not call trySend`() {\n    testWhenOuterFactoryDoesNotCallSend(RX_OBSERVABLE, TRY_SEND)\n  }\n\n  @Test\n  fun `rxFlowable - should fail when outer factor does not call send`() {\n    testWhenOuterFactoryDoesNotCallSend(RX_FLOWABLE, SEND)\n  }\n\n  @Test\n  fun `rxFlowable - should fail when outer factory does not call trySend`() {\n    testWhenOuterFactoryDoesNotCallSend(RX_FLOWABLE, TRY_SEND)\n  }\n\n  private fun testWhenOuterFactoryDoesNotCallSend(method: String, emitter: String) {\n    lint()\n      .files(\n        *files,\n        kotlin(\n            \"\"\"\n            package test\n\n            import kotlinx.coroutines.rx3.$method\n\n            class Foo {\n              fun foo() {\n                $method {\n                    $method {\n                        $emitter(\"foo\")\n                    }\n                }\n              }\n            }\n            \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n            src/test/Foo.kt:7: Hint: $method does not call send() or trySend() [${getError(method)}]\n                $method {\n                ^\n            0 errors, 0 warnings, 1 hint\n            \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `rxObservable - should fail when inner factor does not call send`() {\n    testWhenInnerFactoryDoesNotCallSend(RX_OBSERVABLE, SEND)\n  }\n\n  @Test\n  fun `rxObservable - should fail when inner factory does not call trySend`() {\n    testWhenInnerFactoryDoesNotCallSend(RX_OBSERVABLE, TRY_SEND)\n  }\n\n  @Test\n  fun `rxFlowable - should fail when inner factor does not call send`() {\n    testWhenInnerFactoryDoesNotCallSend(RX_FLOWABLE, SEND)\n  }\n\n  @Test\n  fun `rxFlowable - should fail when inner factory does not call trySend`() {\n    testWhenInnerFactoryDoesNotCallSend(RX_FLOWABLE, TRY_SEND)\n  }\n\n  private fun testWhenInnerFactoryDoesNotCallSend(method: String, emitter: String) {\n    lint()\n      .files(\n        *files,\n        kotlin(\n            \"\"\"\n            package test\n\n            import kotlinx.coroutines.rx3.$method\n\n            class Foo {\n              fun foo() {\n                $method {\n                    $emitter(\"foo\")\n                    $method {\n                      println(\"bar!\")\n                    }\n                }\n              }\n            }\n            \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expect(\n        \"\"\"\n            src/test/Foo.kt:9: Hint: $method does not call send() or trySend() [${getError(method)}]\n                    $method {\n                    ^\n            0 errors, 0 warnings, 1 hint\n            \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `rxObservable - should succeed when inner and outer call send`() {\n    testWhenInnerAndOuterFactoriesCallSend(RX_OBSERVABLE, SEND)\n  }\n\n  @Test\n  fun `rxObservable - should succeed when inner and outer call trySend`() {\n    testWhenInnerAndOuterFactoriesCallSend(RX_OBSERVABLE, TRY_SEND)\n  }\n\n  @Test\n  fun `rxFlowable - should succeed when inner and outer call send`() {\n    testWhenInnerAndOuterFactoriesCallSend(RX_FLOWABLE, SEND)\n  }\n\n  @Test\n  fun `rxFlowable - should succeed when inner and outer call trySend`() {\n    testWhenInnerAndOuterFactoriesCallSend(RX_FLOWABLE, TRY_SEND)\n  }\n\n  /**\n   * @param method a function like \"rxObservable\"\n   * @param emitter an emitter function like \"send\"\n   */\n  private fun testWhenInnerAndOuterFactoriesCallSend(method: String, emitter: String) {\n    lint()\n      .files(\n        *files,\n        kotlin(\n            \"\"\"\n            package test\n\n            import kotlinx.coroutines.rx3.$method\n\n            class Foo {\n              fun foo() {\n                $method {\n                    $emitter(\"foo\")\n                    $method { $emitter(\"foo\") }\n                }\n              }\n            }\n            \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun `rxObservable - should succeed when aliased factory calls send`() {\n    testWhenAliasedFactoryCallsSend(RX_OBSERVABLE, SEND)\n  }\n\n  @Test\n  fun `rxObservable - should succeed when aliased factory calls trySend`() {\n    testWhenAliasedFactoryCallsSend(RX_OBSERVABLE, TRY_SEND)\n  }\n\n  @Test\n  fun `rxFlowable - should succeed when aliased factory calls send`() {\n    testWhenAliasedFactoryCallsSend(RX_FLOWABLE, SEND)\n  }\n\n  @Test\n  fun `rxFlowable - should succeed when aliased factory calls trySend`() {\n    testWhenAliasedFactoryCallsSend(RX_FLOWABLE, TRY_SEND)\n  }\n\n  private fun testWhenAliasedFactoryCallsSend(method: String, emitter: String) {\n    lint()\n      .files(\n        *files,\n        kotlin(\n            \"\"\"\n              package test\n\n              import kotlinx.coroutines.rx3.$method as factory\n\n              class Foo {\n                fun foo() {\n                  factory { $emitter(\"foo\") }\n                }\n              }\n              \"\"\"\n          )\n          .indented(),\n      )\n      .run()\n      .expectClean()\n  }\n\n  private fun getError(method: String) =\n    method.replaceFirstChar {\n      if (it.isLowerCase()) it.titlecase(getDefault()) else it.toString()\n    } + \"DoesNotEmit\"\n\n  private companion object {\n    const val RX_OBSERVABLE = \"rxObservable\"\n    const val RX_FLOWABLE = \"rxFlowable\"\n    const val SEND = \"send\"\n    const val TRY_SEND = \"trySend\"\n\n    val COROUTINE_CONTEXT_API: TestFile =\n      kotlin(\n          \"\"\"\n          package kotlin.coroutines\n\n          interface CoroutineContext\n          \"\"\"\n        )\n        .indented()\n\n    val PRODUCER_SCOPE_API: TestFile =\n      kotlin(\n          \"\"\"\n          package kotlin.coroutines\n\n          class ChannelResult<T>\n\n          interface ProducerScope<in E> {\n              suspend fun send(element: E)\n              fun trySend(element: E): ChannelResult<Unit>\n          }\n          \"\"\"\n        )\n        .indented()\n\n    val RX_OBSERVABLE_API: TestFile =\n      kotlin(\n          \"\"\"\n            package kotlinx.coroutines.rx3\n\n            import io.reactivex.rxjava3.core.Observable\n            import kotlin.coroutines.CoroutineContext\n            import kotlin.coroutines.ProducerScope\n\n            fun <T : Any> rxObservable(\n              context: CoroutineContext,\n              block: suspend ProducerScope<T>.() -> Unit\n            ): Observable<T>\n          \"\"\"\n        )\n        .indented()\n\n    val RX_FLOWABLE_API: TestFile =\n      kotlin(\n          \"\"\"\n            package kotlinx.coroutines.rx3\n\n            import io.reactivex.rxjava3.core.Flowable\n            import kotlin.coroutines.CoroutineContext\n            import kotlin.coroutines.ProducerScope\n\n            fun <T : Any> rxFlowable(\n              context: CoroutineContext,\n              block: suspend ProducerScope<T>.() -> Unit\n            ): Flowable<T>\n          \"\"\"\n        )\n        .indented()\n\n    val OBSERVABLE_API: TestFile =\n      kotlin(\n          \"\"\"\n            package io.reactivex.rxjava3.core\n\n            import io.reactivex.rxjava4.annotations.NonNull\n\n            interface Observable<@NonNull T>\n            \"\"\"\n        )\n        .indented()\n\n    val FLOWABLE_API: TestFile =\n      kotlin(\n          \"\"\"\n            package io.reactivex.rxjava3.core\n\n            import io.reactivex.rxjava4.annotations.NonNull\n\n            interface Flowable<@NonNull T>\n            \"\"\"\n        )\n        .indented()\n\n    val NON_NULL_API: TestFile =\n      kotlin(\n          \"\"\"\n            package io.reactivex.rxjava4.annotations\n\n            annotation class NonNull\n            \"\"\"\n        )\n        .indented()\n\n    val files =\n      arrayOf(\n        COROUTINE_CONTEXT_API,\n        PRODUCER_SCOPE_API,\n        RX_OBSERVABLE_API,\n        RX_FLOWABLE_API,\n        OBSERVABLE_API,\n        FLOWABLE_API,\n        NON_NULL_API,\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/rx/RxSubscribeOnMainDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.rx\n\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\nclass RxSubscribeOnMainDetectorTest : BaseSlackLintTest() {\n\n  private val rxJavaJar3 = rxJavaJar3()\n\n  private val androidSchedulers =\n    java(\n        \"\"\"\n      package io.reactivex.rxjava3.android.schedulers;\n\n      import io.reactivex.rxjava3.core.Scheduler;\n\n      public final class AndroidSchedulers {\n          public static Scheduler mainThread() {\n              return null;\n          }\n      }\n    \"\"\"\n      )\n      .indented()\n\n  override val skipTestModes: Array<TestMode> = arrayOf(TestMode.WHITESPACE, TestMode.PARENTHESIZED)\n\n  override fun getDetector() = RxSubscribeOnMainDetector()\n\n  override fun getIssues() = listOf(RxSubscribeOnMainDetector.ISSUE)\n\n  @Test\n  fun subscribeOnMain_fullyQualified_fails_java() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        java(\n            \"\"\"\n            package com.slack.lint;\n\n            import io.reactivex.rxjava3.core.Observable;\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;\n\n            public class Foo {\n              public void bar(Observable obs) {\n                obs.subscribeOn(AndroidSchedulers.mainThread());\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.java:8: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              obs.subscribeOn(AndroidSchedulers.mainThread());\n                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.java line 8: Replace with observeOn():\n          @@ -8 +8\n          -     obs.subscribeOn(AndroidSchedulers.mainThread());\n          +     obs.observeOn(AndroidSchedulers.mainThread());\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnMain_fullyQualified_fails_kotlin() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Observable\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers\n\n            object Foo {\n              fun bar(obs: Observable<Any>) {\n                obs.subscribeOn(AndroidSchedulers.mainThread())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:8: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              obs.subscribeOn(AndroidSchedulers.mainThread())\n                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.kt line 8: Replace with observeOn():\n          @@ -8 +8\n          -     obs.subscribeOn(AndroidSchedulers.mainThread())\n          +     obs.observeOn(AndroidSchedulers.mainThread())\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnMain_staticImport_fails_java() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        java(\n            \"\"\"\n            package com.slack.lint;\n\n            import io.reactivex.rxjava3.core.Observable;\n            import static io.reactivex.rxjava3.android.schedulers.AndroidSchedulers.mainThread;\n\n            public class Foo {\n              public void bar(Observable obs) {\n                obs.subscribeOn(mainThread());\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.java:8: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              obs.subscribeOn(mainThread());\n                  ~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.java line 8: Replace with observeOn():\n          @@ -8 +8\n          -     obs.subscribeOn(mainThread());\n          +     obs.observeOn(mainThread());\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnMain_staticImport_fails_kotlin() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Observable\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers.mainThread\n\n            class Foo {\n              fun bar(obs: Observable<Any>) {\n                obs.subscribeOn(mainThread())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:8: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              obs.subscribeOn(mainThread())\n                  ~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.kt line 8: Replace with observeOn():\n          @@ -8 +8\n          -     obs.subscribeOn(mainThread())\n          +     obs.observeOn(mainThread())\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnMain_outsideAssignment_field_fails_java() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        java(\n            \"\"\"\n            package com.slack.lint;\n\n            import io.reactivex.rxjava3.core.Observable;\n            import io.reactivex.rxjava3.core.Scheduler;\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;\n\n            public class Foo {\n              private final Scheduler scheduler = AndroidSchedulers.mainThread();\n              public void bar(Observable obs) {\n                obs.subscribeOn(scheduler);\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.java:10: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              obs.subscribeOn(scheduler);\n                  ~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.java line 10: Replace with observeOn():\n          @@ -10 +10\n          -     obs.subscribeOn(scheduler);\n          +     obs.observeOn(scheduler);\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnMain_outsideAssignment_local_fails_java() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        java(\n            \"\"\"\n            package com.slack.lint;\n\n            import io.reactivex.rxjava3.core.Observable;\n            import io.reactivex.rxjava3.core.Scheduler;\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;\n\n            public class Foo {\n              public void bar(Observable obs) {\n                Scheduler scheduler = AndroidSchedulers.mainThread();\n                obs.subscribeOn(scheduler);\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.java:10: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              obs.subscribeOn(scheduler);\n                  ~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.java line 10: Replace with observeOn():\n          @@ -10 +10\n          -     obs.subscribeOn(scheduler);\n          +     obs.observeOn(scheduler);\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnMain_outsideAssignment_fails_field_kotlin() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Observable\n            import io.reactivex.rxjava3.core.Scheduler\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers\n\n            object Foo {\n              private val scheduler = AndroidSchedulers.mainThread()\n              fun bar(obs: Observable<Any>) {\n                obs.subscribeOn(scheduler)\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:10: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              obs.subscribeOn(scheduler)\n                  ~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.kt line 10: Replace with observeOn():\n          @@ -10 +10\n          -     obs.subscribeOn(scheduler)\n          +     obs.observeOn(scheduler)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnMain_outsideAssignment_fails_local_kotlin() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Observable\n            import io.reactivex.rxjava3.core.Scheduler\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers\n\n            object Foo {\n              fun bar(obs: Observable<Any>) {\n                val scheduler = AndroidSchedulers.mainThread()\n                obs.subscribeOn(scheduler)\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:10: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              obs.subscribeOn(scheduler)\n                  ~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.kt line 10: Replace with observeOn():\n          @@ -10 +10\n          -     obs.subscribeOn(scheduler)\n          +     obs.observeOn(scheduler)\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnIo_passes_java() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        java(\n            \"\"\"\n            package com.slack.lint;\n\n            import io.reactivex.rxjava3.core.Observable;\n            import io.reactivex.rxjava3.schedulers.Schedulers;\n\n            public class Foo {\n              public void bar(Observable obs) {\n                obs.subscribeOn(Schedulers.io());\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun subscribeOnIo_passes_kotlin() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Observable\n            import io.reactivex.rxjava3.schedulers.Schedulers\n\n            object Foo {\n              fun bar(obs: Observable<Any>) {\n                obs.subscribeOn(Schedulers.io())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun subscribeOnIo_staticImport_passes_java() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        java(\n            \"\"\"\n            package com.slack.lint;\n\n            import io.reactivex.rxjava3.core.Observable;\n            import io.reactivex.rxjava3.schedulers.Schedulers.io;\n\n            public class Foo {\n              public void bar(Observable obs) {\n                obs.subscribeOn(io());\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .allowCompilationErrors() // Until AGP 7.1.0\n      // https://groups.google.com/g/lint-dev/c/BigCO8sMhKU\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun subscribeOnIo_staticImport_passes_kotlin() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Observable\n            import io.reactivex.rxjava3.schedulers.Schedulers.io\n\n            object Foo {\n              fun bar(obs: Observable<Any>) {\n                obs.subscribeOn(io())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun subscribeOnIo_outsideAssignment_passes_java() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        java(\n            \"\"\"\n            package com.slack.lint;\n\n            import io.reactivex.rxjava3.core.Observable;\n            import io.reactivex.rxjava3.core.Scheduler;\n            import io.reactivex.rxjava3.schedulers.Schedulers;\n\n            public class Foo {\n              private final Scheduler scheduler = Schedulers.io();\n              public void bar(Observable obs) {\n                obs.subscribeOn(scheduler);\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun subscribeOnIo_outsideAssignment_passes_kotlin() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Observable\n            import io.reactivex.rxjava3.schedulers.Schedulers\n\n            object Foo {\n              private val scheduler: Scheduler = Schedulers.io()\n              fun bar(obs: Observable<Any>) {\n                obs.subscribeOn(scheduler)\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun subscribeOnMain_flowable_fails() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Flowable\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers\n\n            object Foo {\n              fun bar(flow: Flowable<Any>) {\n                flow.subscribeOn(AndroidSchedulers.mainThread())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:8: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              flow.subscribeOn(AndroidSchedulers.mainThread())\n                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.kt line 8: Replace with observeOn():\n          @@ -8 +8\n          -     flow.subscribeOn(AndroidSchedulers.mainThread())\n          +     flow.observeOn(AndroidSchedulers.mainThread())\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnIo_flowable_passes() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Flowable\n            import io.reactivex.rxjava3.schedulers.Schedulers\n\n            object Foo {\n              fun bar(flow: Flowable<Any>) {\n                flow.subscribeOn(Schedulers.io())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun subscribeOnMain_maybe_fails() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Maybe\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers\n\n            object Foo {\n              fun bar(maybe: Maybe<Any>) {\n                maybe.subscribeOn(AndroidSchedulers.mainThread())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:8: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              maybe.subscribeOn(AndroidSchedulers.mainThread())\n                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.kt line 8: Replace with observeOn():\n          @@ -8 +8\n          -     maybe.subscribeOn(AndroidSchedulers.mainThread())\n          +     maybe.observeOn(AndroidSchedulers.mainThread())\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnIo_maybe_passes() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Maybe\n            import io.reactivex.rxjava3.schedulers.Schedulers\n\n            object Foo {\n              fun bar(maybe: Maybe<Any>) {\n                maybe.subscribeOn(Schedulers.io())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun subscribeOnMain_single_fails() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Single\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers\n\n            object Foo {\n              fun bar(single: Single<Any>) {\n                single.subscribeOn(AndroidSchedulers.mainThread())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:8: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              single.subscribeOn(AndroidSchedulers.mainThread())\n                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.kt line 8: Replace with observeOn():\n          @@ -8 +8\n          -     single.subscribeOn(AndroidSchedulers.mainThread())\n          +     single.observeOn(AndroidSchedulers.mainThread())\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnIo_single_passes() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Single\n            import io.reactivex.rxjava3.schedulers.Schedulers\n\n            object Foo {\n              fun bar(single: Single<Any>) {\n                single.subscribeOn(Schedulers.io())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expectClean()\n  }\n\n  @Test\n  fun subscribeOnMain_completable_fails() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Completable\n            import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers\n\n            object Foo {\n              fun bar(completable: Completable) {\n                completable.subscribeOn(AndroidSchedulers.mainThread())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/com/slack/lint/Foo.kt:8: Error: This will make the code for the initial subscription (above this line) run on the main thread. You probably want observeOn(AndroidSchedulers.mainThread()). [SubscribeOnMain]\n              completable.subscribeOn(AndroidSchedulers.mainThread())\n                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/com/slack/lint/Foo.kt line 8: Replace with observeOn():\n          @@ -8 +8\n          -     completable.subscribeOn(AndroidSchedulers.mainThread())\n          +     completable.observeOn(AndroidSchedulers.mainThread())\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun subscribeOnIo_completable_passes() {\n    lint()\n      .files(\n        rxJavaJar3,\n        androidSchedulers,\n        kotlin(\n            \"\"\"\n            package com.slack.lint\n\n            import io.reactivex.rxjava3.core.Flowable\n            import io.reactivex.rxjava3.schedulers.Schedulers\n\n            object Foo {\n              fun bar(completable: Completable) {\n                completable.subscribeOn(Schedulers.io())\n              }\n            }\n          \"\"\"\n          )\n          .indented(),\n      )\n      .issues(RxSubscribeOnMainDetector.ISSUE)\n      .allowCompilationErrors() // Until AGP 7.1.0\n      // https://groups.google.com/g/lint-dev/c/BigCO8sMhKU\n      .run()\n      .expectClean()\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/text/SpanMarkPointMissingMaskDetectorTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.text\n\nimport com.android.tools.lint.checks.infrastructure.TestMode\nimport org.junit.Test\nimport slack.lint.BaseSlackLintTest\n\n/** Tests [SpanMarkPointMissingMaskDetector]. */\nclass SpanMarkPointMissingMaskDetectorTest : BaseSlackLintTest() {\n\n  override val skipTestModes: Array<TestMode> = arrayOf(TestMode.PARENTHESIZED)\n\n  override fun getDetector() = SpanMarkPointMissingMaskDetector()\n\n  override fun getIssues() = listOf(SpanMarkPointMissingMaskDetector.ISSUE)\n\n  @Test\n  fun `conforming expression - has clean report`() {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package slack.text\n\n              import android.text.Spanned\n\n              class MyClass {\n                  fun doCheckCorrectly(spanned: Spanned): Boolean {\n                    return spanned.getSpanFlags(Object()) and Spanned.SPAN_POINT_MARK_MASK == Spanned.SPAN_INCLUSIVE_INCLUSIVE\n                  }\n              }\n            \"\"\"\n        )\n        .indented()\n    lint().files(testFile).issues(SpanMarkPointMissingMaskDetector.ISSUE).run().expectClean()\n  }\n\n  @Test\n  fun `violating expression with INCLUSIVE_INCLUSIVE on left - creates error and fix`() {\n    testViolatingExpressionLeft(\"Spanned.SPAN_INCLUSIVE_INCLUSIVE\")\n  }\n\n  @Test\n  fun `violating expression with INCLUSIVE_EXCLUSIVE on left - creates error and fix`() {\n    testViolatingExpressionLeft(\"Spanned.SPAN_INCLUSIVE_EXCLUSIVE\")\n  }\n\n  @Test\n  fun `violating expression with EXCLUSIVE_INCLUSIVE on left - creates error and fix`() {\n    testViolatingExpressionLeft(\"Spanned.SPAN_EXCLUSIVE_INCLUSIVE\")\n  }\n\n  @Test\n  fun `violating expression with EXCLUSIVE_EXCLUSIVE on left - creates error and fix`() {\n    testViolatingExpressionLeft(\"Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\")\n  }\n\n  private fun testViolatingExpressionLeft(markPoint: String) {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package slack.text\n\n              import android.text.Spanned\n\n              class MyClass {\n                  fun doCheckIncorrectly(spanned: Spanned): Boolean {\n                    return spanned.getSpanFlags(Object()) == $markPoint || Spanned.x()\n                  }\n              }\n            \"\"\"\n        )\n        .indented()\n    lint()\n      .files(testFile)\n      .issues(SpanMarkPointMissingMaskDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/text/MyClass.kt:7: Error: Do not check against $markPoint directly. Instead mask flag with Spanned.SPAN_POINT_MARK_MASK to only check MARK_POINT flags. [SpanMarkPointMissingMask]\n                return spanned.getSpanFlags(Object()) == $markPoint || Spanned.x()\n                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/slack/text/MyClass.kt line 7: Use bitwise mask:\n          @@ -7 +7\n          -       return spanned.getSpanFlags(Object()) == $markPoint || Spanned.x()\n          +       return ((spanned.getSpanFlags(Object())) and android.text.Spanned.SPAN_POINT_MARK_MASK) == $markPoint || Spanned.x()\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `violating expression with INCLUSIVE_INCLUSIVE on right - creates error and fix`() {\n    testViolatingExpressionRight(\"SPAN_INCLUSIVE_INCLUSIVE\")\n  }\n\n  @Test\n  fun `violating expression with INCLUSIVE_EXCLUSIVE on right - creates error and fix`() {\n    testViolatingExpressionRight(\"SPAN_INCLUSIVE_EXCLUSIVE\")\n  }\n\n  @Test\n  fun `violating expression with EXCLUSIVE_INCLUSIVE on right - creates error and fix`() {\n    testViolatingExpressionRight(\"SPAN_EXCLUSIVE_INCLUSIVE\")\n  }\n\n  @Test\n  fun `violating expression with EXCLUSIVE_EXCLUSIVE on right - creates error and fix`() {\n    testViolatingExpressionRight(\"SPAN_EXCLUSIVE_EXCLUSIVE\")\n  }\n\n  private fun testViolatingExpressionRight(markPoint: String) {\n    val testFile =\n      kotlin(\n          \"\"\"\n              package slack.text\n\n              import android.text.Spanned.$markPoint\n\n              class MyClass {\n                  fun doCheckIncorrectly(spanned: Spanned): Boolean {\n                    return $markPoint == spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                  }\n              }\n            \"\"\"\n        )\n        .indented()\n    lint()\n      .files(testFile)\n      .issues(SpanMarkPointMissingMaskDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/text/MyClass.kt:7: Error: Do not check against $markPoint directly. Instead mask flag with Spanned.SPAN_POINT_MARK_MASK to only check MARK_POINT flags. [SpanMarkPointMissingMask]\n                return $markPoint == spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/slack/text/MyClass.kt line 7: Use bitwise mask:\n          @@ -7 +7\n          -       return $markPoint == spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n          +       return $markPoint == ((spanned.getSpanFlags(Object())) and android.text.Spanned.SPAN_POINT_MARK_MASK) || isBoolean1() && isBoolean2()\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `violating expression with fully qualified - creates error and fix`() {\n    val markPoint = \"android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE\"\n    val testFile =\n      kotlin(\n          \"\"\"\n              package slack.text\n\n              class MyClass {\n                  fun doCheckIncorrectly(spanned: Spanned): Boolean {\n                    return $markPoint == spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                  }\n              }\n            \"\"\"\n        )\n        .indented()\n    lint()\n      .files(testFile)\n      .issues(SpanMarkPointMissingMaskDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/text/MyClass.kt:5: Error: Do not check against android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE directly. Instead mask flag with Spanned.SPAN_POINT_MARK_MASK to only check MARK_POINT flags. [SpanMarkPointMissingMask]\n                return $markPoint == spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/slack/text/MyClass.kt line 5: Use bitwise mask:\n          @@ -5 +5\n          -       return $markPoint == spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n          +       return $markPoint == ((spanned.getSpanFlags(Object())) and android.text.Spanned.SPAN_POINT_MARK_MASK) || isBoolean1() && isBoolean2()\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `violating expression with not equal - creates error and fix`() {\n    val markPoint = \"android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE\"\n    val testFile =\n      kotlin(\n          \"\"\"\n              package slack.text\n\n              class MyClass {\n                  fun doCheckIncorrectly(spanned: Spanned): Boolean {\n                    return $markPoint != spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                  }\n              }\n            \"\"\"\n        )\n        .indented()\n    lint()\n      .files(testFile)\n      .issues(SpanMarkPointMissingMaskDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/text/MyClass.kt:5: Error: Do not check against android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE directly. Instead mask flag with Spanned.SPAN_POINT_MARK_MASK to only check MARK_POINT flags. [SpanMarkPointMissingMask]\n                return $markPoint != spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/slack/text/MyClass.kt line 5: Use bitwise mask:\n          @@ -5 +5\n          -       return $markPoint != spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n          +       return $markPoint != ((spanned.getSpanFlags(Object())) and android.text.Spanned.SPAN_POINT_MARK_MASK) || isBoolean1() && isBoolean2()\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `violating expression with identity equality- creates error and fix`() {\n    val markPoint = \"android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE\"\n    val testFile =\n      kotlin(\n          \"\"\"\n              package slack.text\n\n              class MyClass {\n                  fun doCheckIncorrectly(spanned: Spanned): Boolean {\n                    return $markPoint === spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                  }\n              }\n            \"\"\"\n        )\n        .indented()\n    lint()\n      .files(testFile)\n      .issues(SpanMarkPointMissingMaskDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/text/MyClass.kt:5: Error: Do not check against android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE directly. Instead mask flag with Spanned.SPAN_POINT_MARK_MASK to only check MARK_POINT flags. [SpanMarkPointMissingMask]\n                return $markPoint === spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/slack/text/MyClass.kt line 5: Use bitwise mask:\n          @@ -5 +5\n          -       return $markPoint === spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n          +       return $markPoint === ((spanned.getSpanFlags(Object())) and android.text.Spanned.SPAN_POINT_MARK_MASK) || isBoolean1() && isBoolean2()\n        \"\"\"\n          .trimIndent()\n      )\n  }\n\n  @Test\n  fun `violating expression with not identity equality - creates error and fix`() {\n    val markPoint = \"android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE\"\n    val testFile =\n      kotlin(\n          \"\"\"\n              package slack.text\n\n              class MyClass {\n                  fun doCheckIncorrectly(spanned: Spanned): Boolean {\n                    return $markPoint !== spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                  }\n              }\n            \"\"\"\n        )\n        .indented()\n    lint()\n      .files(testFile)\n      .issues(SpanMarkPointMissingMaskDetector.ISSUE)\n      .run()\n      .expect(\n        \"\"\"\n          src/slack/text/MyClass.kt:5: Error: Do not check against android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE directly. Instead mask flag with Spanned.SPAN_POINT_MARK_MASK to only check MARK_POINT flags. [SpanMarkPointMissingMask]\n                return $markPoint !== spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          1 errors, 0 warnings\n        \"\"\"\n          .trimIndent()\n      )\n      .expectFixDiffs(\n        \"\"\"\n          Fix for src/slack/text/MyClass.kt line 5: Use bitwise mask:\n          @@ -5 +5\n          -       return $markPoint !== spanned.getSpanFlags(Object()) || isBoolean1() && isBoolean2()\n          +       return $markPoint !== ((spanned.getSpanFlags(Object())) and android.text.Spanned.SPAN_POINT_MARK_MASK) || isBoolean1() && isBoolean2()\n        \"\"\"\n          .trimIndent()\n      )\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/java/slack/lint/util/LintUtilsTest.kt",
    "content": "// Copyright (C) 2021 Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0\npackage slack.lint.util\n\nimport com.google.common.truth.Truth.assertThat\nimport org.junit.Test\n\nclass LintUtilsTest {\n  @Test\n  fun snakeToCamelTests() {\n    \"noChange\" assertSnakeToCamel \"noChange\"\n    \"NoChange\" assertSnakeToCamel \"NoChange\"\n    \"test-value\" assertSnakeToCamel \"testValue\"\n    \"test_value\" assertSnakeToCamel \"testValue\"\n    \"test_value_multiple\" assertSnakeToCamel \"testValueMultiple\"\n    \"multi__scores\" assertSnakeToCamel \"multiScores\"\n    \"trailing__\" assertSnakeToCamel \"trailing\"\n    \"___leading\" assertSnakeToCamel \"leading\"\n  }\n\n  @Test\n  fun toScreamingSnakeTests() {\n    \"camelCase\" assertCamelToScreamingSnake \"CAMEL_CASE\"\n    \"CapCamelCase\" assertCamelToScreamingSnake \"CAP_CAMEL_CASE\"\n    \"NO_CHANGE\" assertCamelToScreamingSnake \"NO_CHANGE\"\n    \"test-value\" assertCamelToScreamingSnake \"TEST_VALUE\"\n    \"test_value\" assertCamelToScreamingSnake \"TEST_VALUE\"\n    \"test_value_multiple\" assertCamelToScreamingSnake \"TEST_VALUE_MULTIPLE\"\n    \"multi__scores\" assertCamelToScreamingSnake \"MULTI_SCORES\"\n    \"trailing__\" assertCamelToScreamingSnake \"TRAILING\"\n    \"___leading\" assertCamelToScreamingSnake \"LEADING\"\n  }\n\n  private infix fun String.assertSnakeToCamel(other: String) {\n    assertThat(this.snakeToCamel()).isEqualTo(other)\n  }\n\n  private infix fun String.assertCamelToScreamingSnake(other: String) {\n    assertThat(this.toScreamingSnakeCase()).isEqualTo(other)\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/resources/com/slack/lint/data/testStubs/ViewContextDetectorTestContentProvider.java",
    "content": "package foo;\nimport android.content.ContentProvider;\nimport android.content.Context;\npublic abstract class DummyProvider extends ContentProvider {\n  public void bar() {\n    Context c = getContext();\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/resources/com/slack/lint/data/testStubs/ViewContextDetectorTestCustomViewInternalCaller.java",
    "content": "package foo;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.widget.TextView;\npublic class Example extends TextView {\n  public Example(Context context, AttributeSet attrs) {\n    super(context, attrs);\n  }\n  public void bar() {\n    Activity a = (Activity) getContext();\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/resources/com/slack/lint/data/testStubs/ViewContextDetectorTestExternalCallerOnCustomView.java",
    "content": "package foo;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.widget.TextView;\npublic class Example {\n  TextView view;\n  public Example(TextView v) {\n    view = v;\n  }\n  public void bar() {\n    Activity activity = (Activity) view.getContext();\n  }\n}\n"
  },
  {
    "path": "slack-lint-checks/src/test/resources/com/slack/lint/data/testStubs/ViewContextDetectorTestExternalCallerOnView.java",
    "content": "package foo;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.View;\npublic class Example {\n  View view;\n  public Example(View v) {\n    view = v;\n  }\n  public void bar() {\n    Activity activity = (Activity) view.getContext();\n  }\n}\n"
  },
  {
    "path": "spotless/spotless.kt",
    "content": "// Copyright (C) $YEAR Slack Technologies, LLC\n// SPDX-License-Identifier: Apache-2.0"
  }
]